LiveScript 살펴보기 – 04 제어문

이번 글에서는 LS의 기본적인 제어 구문인 반복문과 분기문을 작성하는 방법에 대해서 살펴볼 예정이다. LS의 특징인 “거의 모든 것은 표현식이다”라는 점에서 for/while 문 자체도 하나의 표현식으로 최종 표현식의 결과를 모은 리스트로 평가될 수 있다는 점을 놓치지 말자.

IF에 의한 분기

if를 통한 분기의 패턴을 살펴보자. if 조건 then 식1 else 식 2의 형태가 기본 꼴이며, then을 생략하고 들여쓰기 하는 것이 가능하다.if

if 2 + 2 == 4
  1
else
  2
## 1로 평가된다.

if 절은 값 뒤에 붙어서 특정 조건일 때만 표현식이 성립하도록 한다. 이는 파이썬의 X if A else Y 와는 다르다. LS에서는 뒤에 덧붙이는 if 절을 이용해서 3항 연산자를 흉내내는 표현은 쓰지 않는다. if A then X else Y 의 표현 그대로를 쓴다.

x = 10
x = 3 if 2 + 2 is 4

unlessif not과 같은 표현이며, 용법 자체는 완전히 동일하다.

unless 2 + 2 == 5
  'something'

## 표현식 뒤에 쓰여서 한 줄처럼 쓰일 수 있다. 
x = 10
x = 3 unless 2 + 2 == 5

that

that은 조건에 걸린 표현식 자체를 의미한다. 주로 true/false보다는 해당 표현이 존재하느냐 (!null && !undefined)에 관하여 평가할 때 사용한다.

time = days: 365
half-year = that / 2 if time.months  ## time.month 속성있다면 그 절반값

## 사실 이 표현은 아래와 같이 쓰는 편이 좀 더 명확하다
half-year = that / 2 if time.months?

반복

LS의 for 반복문은 크게 세 가지 기본 형태를 가진다. 첫째로 특정한 범위의 숫자값을 이터레이션하는 것과 둘째로 리스트의 각 원소를 순회하는 것 그리고 세번째로 특정한 객체의 키:값 쌍을 순회하는 것이 그것이다.

범위 순회

특정한 범위의 숫자값 사이를 순회하는 것은 다음과 같은 패턴을 가진다,

for (let) (VAR) (from NUM) to|til NUM (by NUM) (when CND)

이 패턴에서 거의 대부분의 요소는 선택적이다.

  • let이 들어가면 for 문의 반복되는 몸체가 익명의 함수로 정의되고, 반복문 내에서는 해당함수를 실행한다.
  • from 은 생략되면 0부터 시작한다.
  • to,til 은 숫자 범위 리터럴의 의미와 일치한다.
  • by는 기본적으로 1이며 (종단값이 더 작으면 -1)
  • when 으로 추가적인 조건을 더 붙일 수 있는데 이는 case, |로도 대체하여 쓸 수 있다.

 

for let x from 0 til 40 by 2 when x % 5 == 0
  x * 2

for 문 자체가 하나의 표현식이다. 따라서 for 문을 돌고난 모든 결과는 하나의 리스트로 변환되어 평가될 수 있다.

리스트 이터레이션

for ... in 루프는 리스트의 원소를 하나씩 순회하는 개념이다.

for (let) VAR (, INDEX) in EXP (by NUM) (when COND)

흔히 지나치기 쉬운 표현이 for value, index in array 의 형태인데, 부가적으로 (, index)를 쓰면 각 원소의 인덱스값을 함께 쓰는 것이 가능하다. 구간 순회와 마찬가지로 let 을 쓰면 반복문의 본체를 별도의 함수로 만들 수 있으며, 그 결과는 다시 하나의 표현식으로 평가되어 리스트가 된다.

## 리스트 원소 순회
for x in [1 2 3]
  x

## 위 블럭자체는 평가되고 나면 리스트가 된다...

xs = for let x, i in [1 to 10] by 2 | x % 3 == 0
  -> x + i 
## xs는 함수의 리스트이다.

xs[0]! #=> 5
xs[1]! #=> 17

객체 이터레이션

for ... of 는 객체 프로퍼티에 대해서 각 키를 순회한다. 하지만 몇 가지 옵셔널한 장치를 사용하여 보다 풍부한 표현을 지원한다.

for (own) (let) KEY (, VALUE) of EXP (when COND)

  • own 을 쓰면 해당 객체 자신에게만 있는 (프로토타입으로부터 상속받지 않은) 프로퍼티만 처리한다.
  • key 대신 key, value 를 써서 키와 값을 동시에 맵핑하여 순회할 수 있다.
  • when을 써서 추가적인 조건을 걸 수 있다.

이 역시 결과는 본체 표현식의 리스트와 같은 형태로 보인다.

표현식으로서의 반복문

표현식으로서의 반복문은 리스트로 평가된다. for 문 내에서 다른 for문을 중첩하는 것도 가능하며, 이 경우에는 중첩된 리스트의 리스트를 얻을 수 있다. 다음은 2단부터 9단까지의 전체 구구단을 문자열로 생성한다.

## 구구단 생성하기

result = for x from 2 to 9
  for y from 2 to 9
    "#x * #y = #{x*y}"
|> (.map (.join \n))
|> (.join '\n\n----\n\n')

console.log result

THEN

for 뒤에 then을 붙여 쓸 수 있다. 이는 주로 블럭이 아닌 단일 표현식을 반복하여 평가할 때, 코드를 간결하게 만드는데 도움이 된다. for til 3 then func!func를 세 번 실행한다.

리스트 축약

LS의 리스트 축약 문법은 for 절의 사용법이 LS의 규칙을 따른다는 것을 제외하면 파이썬의 그것과 대동소이하다. 리스트 축약 문법은 항상 리스트를 생성하며, 중첩된 축약은 리스트의 리스트가 아닌 펼쳐진 리스트를 만든다. (리스트의 리스트를 만드려면 리스특 축약 문법 내에 리스트 축약 문법을 중첩하여 사용해야 한다.

## list comprehension
## [ {V에 대한 표현식} for V in A_LIST (WHEN CONDITION)]

## 두 리스트의 각 원소를 이어붙인 조합을 만든다. 
["#x#y" for x in <[ a b ]> for y in [1 2]]
## ['a', 'b']와 [1, 2]의 각 원소를 조합한다.

축약 문법은 항상 one-liner일 필요는 없다. 그리고 좀 더 보기 좋게 쓰기 위해 들여쓰기를 적극적으로 도입할 수 있다.

## 두 테이블을 join한다.
[{id: id1, name, age} for {id: id1, name} in table1
                      for {id: id2, age} in table
                      when id1 is id2]

리스트 축약 내의 캐스케이드

..가 포함되는 표현식을 사용하면, for에서 ... in 부분이 생략될 수 있기 때문에 축약 문법을 매우 단축할 수 있다.

## [x + 1 for x in [1 2 3]]
[..+1 for [1 2 3]] #=> [2,3,4]

list-of-obj =
  * name: \Alice
    age: 23
  * name: \Betty
    age: 26
[..name for list-of-obj]
## <[Alice Betty]>

오브젝트 축약

{ ... } 속에 [KEY, VALUE] 형태로 반복되는 값을 넣으면 이를 축약해서 object로 만들어준다. 문법이 헷갈리기 쉬운데1 주의하자. (그런데 막상 생각해보면 또 그닥…)

## 특정 객체의 값을 2배씩한 사본을 만든다.
{[k, v*2] for k,v of {a:1, b:2}}

While 루프

while 문 역시 제공되며, while not의 동치로 until을 사용할 수 있다. while, until 루프 역시 본체의 가장 마지막 표현식이 누적되어 리스트로 평가된다.

i = 1
while i < 10 
  i += 1
  i * 2   ## 반복문 블럭의 맨 마지막 표현식누적되어 리스트가 된다. 

# => [2 4 6 ... 20]

do … while 조건

판별식을 뒤로 보내는 경우 do ... while COND 형태로 작성하는 것도 가능하다. 이 역시 JS의 문법과 동일하다.

무한루프

loop 라고만 쓰면 무한 루프를 만들 수 있다. 비슷하게 for ever를 쓸 수 있다.

SWITCH에 의한 분기

Switch 구문에 의한 다중 분기는 switch 조건 뒤에 각 케이스마다 case를 붙여서 사용한다. JS와 달리 모든 케이스는 자동으로 브레이크된다. 매칭 값이 두 개 이상인 경우에는 ,를 사용하여 연결한다.

매 케이스는 자동으로 브레이크가 삽입되기 때문에 반대로 fallthrough를 써서 다음 조건에 대해 실행하게 하는 것도 가능하다. 또, case ... then| ... => 으로 바꿔 쓸 수 있다. 이때, |_,|otherwise를 써서 디폴트 동작을 정의한다.

## switch 구문
switch n
  case 1 then \hello
  case 2, 4 then \boon
  | 6 =>  'here it is'  ## case 6 then 
  default \something

switch에 조건이 따로 주어지지 않은 경우에는 각 case는 주어진 조건식을 판별하여 참이면 실행한다. 또한 if 와 마찬가지로 that을 써서 조건값을 참고할 수 있는데, 이 때 조건값은 case 절에 있는 것이 아닌 switch 다음에 오는 것을 참조한다.

자동 분기

->, :, = 뒤에 case 토큰이 오는 경우는 자동으로 switch 문으로 인식된다.

## 자동 switch

func = (param) ->
  | param.length < 5 => param.length
  | otherwise => param.slice 3

## 하스켈의 가드와 비슷하다.

## compiled
# var func = function(param) {
#   if (param.length < 5) { return param.length; }
#   return param.slice(3)
# };

Coffeescript

LS가 커피스크립트의 방언인 관계로, 커피스크립트의 when COND then EXP ... else EXP 형태의 구문도 역시 지원한다.


  1. 파이썬에서는 { key:value for ... }와 같은식으로 사전을 만드니까…