커피스크립트 기본 : 값, 변수, 제어 흐름 (2)

등록일: 2014. 11. 06

시작하세요! 커피스크립트 프로그래밍: 우아하고 간결한 자바스크립트 코딩

  • E. 호이가르드 지음
  • 유윤선 옮김
  • 268쪽
  • 22,000원
  • 2012년 12월 6일

표준 환경에서 제공하는 많은 값은 ‘function’ 타입을 갖고 있다. 함수는 값으로 감싼 프로그램 조각이다. 일반적으로 이런 함수는 유용한 작업을 수행하며, 함수를 포함하는 함수 값을 사용해 호출할 수 있다. 개발 환경에서는 show 변수가 터미널이나 명령행 창에서 메시지를 보여주는 함수를 담고 있다. 이 함수는 다음과 같이 사용할 수 있다.

show 'Also, your hair is on fire.'

함수 내의 코드를 실행하는 것을 ‘함수를 호출한다’ 또는 ‘함수를 적용한다’라고 한다. 함수를 호출하려면 함수명 다음에 괄호를 입력하거나 콤마로 구분한 값 목록을 지정하면 된다. 함수 값을 생성하는 모든 표현식은 표현식 다음에 괄호를 사용해 호출할 수 있다. 값을 전달할 때는 괄호를 생략할 수 있다. 문자열 값은 함수로 전달된 후 콘솔 창에 텍스트를 보여주는 데 사용된다. 함수로 전달되는 값은 파라미터 또는 인자라고 한다. show는 한 개의 파라미터만 필요하지만 다른 함수는 여러 개의 파라미터가 필요할 수 있다.


메시지 표시는 부수 효과다. 많은 함수는 함수가 생성하는 부수 효과로 인해 유용하다. 함수가 값을 생산할 수도 있는데, 이때는 유용한 부수 효과가 없어도 된다. 예를 들어 Math.max 함수는 두 개의 인자를 받고 둘 중 더 큰 수를 반환한다.

show Math.max 2, 4

함수가 값을 생산할 때는 ‘함수가 값을 반환한다’라고 한다. 값을 생산하는 대상은 커피스크립트에서 항상 표현식에 해당하므로 함수 호출은 큰 표현식의 일부로 사용할 수 있다.

show 100 + Math.max 7, 4
show Math.max(7, 4) + 100
show Math.max(7, 4 + 100)
show Math.max 7, 4 + 100

함수 호출에서 괄호를 생략하면 커피스크립트는 암시적으로 줄 끝까지 이어지는 괄호를 삽입한다. 따라서 위의 예제의 경우 처음 두 줄의 결과는 107이고, 마지막 두 줄의 결과는 104가 된다. 따라서 자신의 의도대로 원하는 결과를 얻기 위해 괄호를 사용해야 할 때가 있다. 함수 장에서는 나만의 함수를 직접 작성하는 법을 설명한다.


앞의 결과에서 볼 수 있듯이 show 함수는 표현식의 결과를 보여주는 데 사용하기 편리하다. show는 표준 커피스크립트 함수가 아니며, 브라우저가 제공하지도 않는다. 이 함수는 Smooth CoffeeScript prelude를 통해 제공된다. 웹 브라우저에서 작업할 때는 환경이 다르므로 alert을 사용해 메시지 대화상자를 띄울 수 있다. 이 책에서는 계속해서 커피스크립트 환경을 사용한다. show는 인자를 프로그램 내에서 인자가 보이는 형태대로 표시함으로써 값 타입에 대한 정보를 더 많이 제공한다. coffee -r ./prelude로 시작한 인터랙티브 콘솔에서 이 환경은 다음과 같이 살펴볼 수 있다.

show process
show console
show _
show show

지금은 출력 결과는 크게 중요하지 않다. show는 프로그램 내 요소에 대한 상세 정보를 제공해줄 수 있는 툴이며, 나중에 예상대로 프로그램이 동작하지 않을 때 편리하게 활용할 수 있다.


브라우저가 제공하는 환경에는 창을 팝업으로 보여줄 수 있는 몇 개의 함수만 들어 있다. conrm을 사용하면 사용자에게 확인/취소를 물을 수 있다. 이 함수는 사용자가 확인을 누르면 true, 취소를 누르면 false에 해당하는 불리언 값을 반환한다. prelude에는 사용자가 질문에 예 또는 아니오로 답할 수 있는 유사한 conrm 함수가 들어 있다.

커피스크립트 환경은 서버로 실행하게끔 최적화됐으므로 사용자가 응답하기를 기다리지 않는다. 대신 함수 호출 후 다음 코드를 계속해서 실행한다. 사용자가 질문에 답하면 인자로 지정한 함수가 사용자의 대답과 함께 호출된다. 여기에는 약간의 마법이 들어 있지만 자세한 내용은 함수 장에서 설명한다. 이 사용 방식은 좀 더 복잡하지만 이후 장에서는 많은 사용자를 대상으로 하는 웹 애플리케이션에서 이 함수를 사용하기 적합한 경우를 살펴본다.

confirm 'Shall we, then?', (answer) -> show answer

prompt는 ‘열린’ 질문을 묻는 데 사용한다. 첫 번째 인자는 질문이고 두 번째 인자는 사용자가 처음 시작하는 텍스트다. 창에는 텍스트 한 줄을 입력할 수 있고 이 함수는 (브라우저에서) 이를 문자열로 반환한다. conrm과 마찬가지로 prelude는 이와 유사한 함수를 제공하며, 이 함수는 답을 받는 세 번째 인자를 받는다.

prompt 'Tell us everything you know.', '...',
    (answer) -> show 'So you know: ' + answer

환경 내 거의 모든 변수에 새 값을 지정할 수 있다. 이는 도움이 되기도 하지만 위험하기도 하다. 예를 들어 show에 8을 값으로 지정하면 더는 아무것도 보여줄 수 없다. conrm과 prompt 같은 일부 함수는 파일에서 프로그램을 실행할 때도 동작하지만 인터랙티브 환경과는 잘 연동되지 않는다. 다행히 CTRL+C를 눌러 프로그램을 중단하고 마지막으로 작업하던 곳에서 다시 시작할 수 있다.


한 줄 프로그램은 별로 재미가 없다. 프로그램에 두 줄 이상 명령을 집어넣으면 명령은 한 번에 하나씩 위에서 아래로 실행된다.

prompt 'Pick a number', '', (answer) ->
    theNumber = Number answer
    show 'Your number is the square root of ' +
        (theNumber * theNumber)

Number 함수는 값을 숫자로 변환한다. 이 예제에서는 prompt의 답이 문자열 값이므로 이 함수를 사용한다. 이와 비슷한 함수로 값을 문자열과 불리언으로 변환하는 String과 Boolean 함수도 있다.


0부터 12까지 모든 짝수를 출력하는 프로그램을 생각해 보자. 이런 프로그램을 작성하는 방법 중 하나는 다음과 같다.

show 0
show 2
show 4
show 6
show 8
show 10
show 12

물론 이렇게 해도 되지만 프로그램을 작성하는 주된 이유는 더 많은 일을 하는 게 아니라 더 적은 일을 하기 위한 것이다. 만일 1,000 미만의 모든 짝수를 출력해야 한다면 앞의 방식은 사용할 수 없을 것이다. 우리에게는 코드를 자동으로 반복할 수 있는 방법이 필요하다.

currentNumber = 0
while currentNumber <= 12
    show currentNumber
    currentNumber = currentNumber + 2

소개 장에서 본 while을 기억할 것이다. while로 시작하는 명령은 순환문을 생성한다. 순환문은 명령의 순서에 개입해 프로그램이 특정 명령을 여러 번 반복하게 해준다. 이 경우 while 단어 다음에 표현식이 나오는데, 이 표현식은 순환문을 반복할지 또는 종료할지 결정하는 데 사용된다. 이 표현식의 불리언 값이 true인 한 순환문 내의 코드가 반복된다. 이 표현식 값이 false가 되면 프로그램은 바로 순환문 아래로 이동하고 평상시처럼 계속 진행한다.

currentNumber 변수는 프로그램의 진행 과정을 추적하는 변수 사용법을 보여준다. 순환문이 반복할 때마다 이 변수는 2만큼 증가하며, 매 반복을 시작할 때 12라는 숫자와 비교해 순환문을 계속할지 판단한다.

while 명령문의 세 번째 요소는 또 다른 명령문이다. 이 명령문은 순환문의 본체로, 여러 번 수행해야 하는 행동(들)을 나타낸다. 여기서는 블록 내 명령을 그룹으로 지정하기 위해 들여쓰기를 사용했다. 블록 바깥 세계에서는 블록을 단일 명령으로 간주한다. 이 예제에서는 show를 호출하고 currentNumber를 업데이트하는 명령을 포함시키는 데 블록을 사용했다.

만일 숫자를 출력하지 않아도 된다면 이 프로그램은 다음과 같이 작성할 수 있다.

counter = 0

while counter <= 12 then counter = counter + 2

여기서 counter = counter + 2는 순환문의 본체를 형성하는 명령이다. then 키워드는 본체와 불리언을 분리해 둘을 한 줄에 쓸 수 있게 해준다.

연습문제 2

지금까지 배운 기법을 활용해 210(2의 10승) 값을 계산하고 보여주는 프로그램을 작성하자. 물론 2 * 2 *... 같은 간단한 트릭을 사용하더라도 상관없다.

이 프로그램을 작성하는 게 어렵다면 짝수 번호 예제의 관점에서 이 문제를 살펴보려고 노력하자. 이 프로그램은 일정한 횟수만큼 행동을 반복해야 한다. 이 프로그램에는 while 순환문과 더불어 카운터 변수를 사용할 수 있다. 이번에는 카운터를 출력하는 대신 프로그램이 특정 값에 2를 곱해야 한다. 이 값은 다른 변수에 저장해, 매 반복마다 결과 값을 계속 키워야 한다.

아직 이 프로그램을 어떻게 개발해야 할지 감이 잡히지 않더라도 걱정하지 말자. 이 장에서 다룬 기법을 완벽하게 이해하더라도 이를 특정 문제에 적용하기는 어려울 수 있다. 코드를 읽고 작성하다 보면 문제에 대한 감을 익히는 데 도움이 되므로 풀이를 연구해 보고 다음 연습문제에 도전해 보자.

풀이

result = 1
counter = 0
while counter < 10
    result = result * 2
    counter = counter + 1
show result

카운터는 1부터 시작해 <= 10인지 검사할 수도 있지만 0부터 시작하는 습관을 들이는 게 좋다(이유는 나중에 설명한다).

물론 여러분이 작성한 풀이는 필자의 풀이와 완전히 일치하지 않아도 되며, 제대로 동작하기만 하면 된다. 혹시 자신의 풀이가 필자의 것과 크게 다르다면 필자의 풀이를 이해하고 넘어가기 바란다.

연습문제 3

앞의 연습문제를 조금만 수정하면 삼각형을 그리는 데 활용할 수 있다. 여기서 ‘삼각형을 그린다’는 말은 ‘삼각형처럼 보이는 텍스트를 출력한다’는 뜻이다.

10개의 줄을 출력하자. 첫 번째 줄에는 ‘#’ 문자가 하나뿐이다. 두 번째 줄에는 ‘#’ 문자가 두 개 있다. 이런 식으로 각 줄마다 ‘#’ 문자를 추가한다.

그럼 X개의 ‘#’이 들어 있는 문자열을 어떻게 만들 수 있을까? 한 가지 방법은 ‘내부 순환문’을 사용해 필요한 문자열을 생성하는 것이다. 내부 순환문은 순환문 내에 있는 순환문이다. 간단한 방법은 지난 번 반복 때 사용한 문자열을 재사용해 글자를 한 개만 추가하는 것이다.

풀이

line = ''
counter = 0
while counter < 10
    line = line + '#'
    show line
    counter = counter + 1

이 풀이에서 일부 명령 앞에 둔 공백에 주의하자. 이런 공백은 꼭 필요하다. 들여쓰기 수준은 각 줄이 속하는 블록을 나타낸다. 블록 내 들여쓰기의 역할은 읽기 쉽게끔 코드의 구조를 정해주는 것이다. 새 블록은 다른 블록 내에 열려 있을 수 있으므로 들여쓰기를 하지 않으면 한 블록이 어디에서 끝나고 다른 블록이 어디에서 시작하는지 알기 어렵다. 줄을 들여쓰면 프로그램의 시각적인 모양이 프로그램 내 블록의 모양과 일치하게 된다. 필자는 열려 있는 블록에는 두 개의 공백을 즐겨 쓰지만 이에 대한 취향은 사람마다 다르다. 줄이 지나치게 길다면 줄을 두 개의 단어로 나누거나 줄 끝에 \를 두고 다음 줄에서 이어서 쓸 수 있다.


지금까지 살펴본 while의 사용 패턴은 모두 동일하다. 먼저 ‘counter’ 변수를 생성한다. 이 변수는 순환문의 진행 과정을 추적한다.

while 그 자체에는 조건문이 들어 있으며, 보통 카운터가 경계 값에 도달했는지 여부를 검사한다. 그런 다음 순환문 본체의 끝에서는 카운터를 업데이트한다.

많은 순환문이 이런 패턴을 따른다. 이런 이유로 커피스크립트 및 유사 언어는 좀 더 간결하고 이해하기 쉬운 구문을 함께 제공한다.

for number in [0..12] by 2 then show number

이 프로그램은 앞서 짝수 숫자를 출력하는 예제와 동일한 기능을 한다. 달라진 점은 순환문의 ‘상태’와 관련된 모든 명령을 이제 한 줄로 표현한다는 점이다. 대괄호 안에 있는 숫자는 범위로, [4..7]은 첫 번째 숫자부터 시작해 마지막 숫자까지 1씩 증가하는 숫자 목록을 나타낸다. 목록에서 두 개의 점을 사용하면 마지막 숫자를 포함하고(4,5,6,7), [4...7]처럼 세 개의 점을 사용하면 마지막 숫자를 제외(4,5,6)한다. 각 단계의 양은 by 키워드를 통해 변경한다. 따라서 [2..6] by 2는 (2,4,6) 숫자 목록에 해당한다. 첫 번째 숫자가 가장 크면 범위 값을 감소시킬 수도 있으며, 범위 값에는 음수 또는 부동 소수도 포함시킬 수 있다.

for 문에서 number를 사용하면 순환문을 반복할 때마다 숫자 범위 내에서 매번 다음 숫자를 가져온다. 순환문 내에서는 number 변수를 연산에 사용하거나 다음 예제에서처럼 show number에 사용할 수 있다. 대부분의 경우 while보다는 for가 더 짧고 명료하다.

for 문도 여러 형태로 구성할 수 있다. 한 가지 방법으로 순환문의 본체를 for 문 앞에 두는 방식이 있다.

# 본체를 들여쓴 for문
for number in [0..12] by 2
    show number

# 본체를 앞에 쓴 for문
show number for number in [0..12] by 2

앞에서 ‘#’으로 시작하는 줄이 조금 이상해 보일 수 있다. 이 줄은 프로그램에서 추가 텍스트를 기입할 때 종종 사용한다. #은 주로 프로그램에 사람의 언어로 된 설명을 추가할 때 사용한다.

# counter 변수를 정의
# 이 변수 값은 0으로 시작
counter = 0

# 이제 순환문을 시작
while counter < 100 # counter가 100보다 작으면
    ###
    순환문을 반복할 때마다 counter 값을 증가시킴
    여기서는 1씩 값을 증가시킴
    ###
    counter++
# 이제 모든 작업 종료

이런 형태의 텍스트를 주석이라고 한다. 주석의 적용 방식은 다음과 같다. ‘#’은 주석을 시작하고, 이 주석은 줄 끝까지 적용된다. ‘###’은 또 다른 종류의 주석을 시작하고 ‘###’가 다시 나올 때까지 여러 줄에 걸쳐 주석을 적용한다.

앞에서 볼 수 있듯이 간단한 프로그램이라도 많은 주석을 추가하는 것만으로 크고, 지저분하고 복잡해 보일 수 있다.


지금까지 일부 변수명에서 이상한 대소문자 표기를 사용했다. 변수명 사이에는 공백이 올 수 없으므로(컴퓨터는 이를 두 개의 각기 다른 변수로 이해한다) 몇 개의 단어로 이뤄진 변수명은 다음과 같은 형태의 이름으로밖에 지정할 수 없다.

fuzzylittleturtle     FuzzyLittleTurtle
fuzzy_little_turtle   fuzzyLittleTurtle

이 중 첫 번째 변수는 읽기 어렵다. 개인적으로 필자는 밑줄을 사용하기를 좋아하지만 밑줄을 사용한 변수는 입력하기 번거롭다. 하지만 커피스크립트는 자바스크립트에서 발전했으므로 대부분의 커피스크립트 프로그래머는 자바스크립트 관례를 따라 마지막 변수 명명 방식을 사용한다. 이 방식은 표준 자바스크립트 함수에서 사용하는 방식이다. 이런 관례를 따르는 것은 어렵지 않으므로 여기서는 다른 개발자들을 따라 첫 번째 글자 이후 새로 나오는 모든 단어의 첫 글자를 대문자로 표기한다. Number 함수 같은 일부 사례에서는 변수의 첫 글자가 대문자인 경우도 있다. 이는 이 함수를 생성자로 표시하기 위한 것이다. 생성자가 뭔지는 ‘객체지향’ 장에서 다룬다. 지금은 이처럼 일관성이 다소 떨어지는 결과를 보더라도 신경 쓰지 않아도 된다.

while과 for처럼 특수 의미가 있는 이름은 변수명으로 사용할 수 없다는 데 주의하자. 이들 이름은 키워드라고 한다. 자바스크립트 및 커피스크립트의 미래 버전에서 ‘사용하기로 예약된’ 단어도 있다. 이들 단어도 공식적으로 변수명으로 사용할 수 없다(일부 환경에서는 이를 허용하긴 하지만). 예약어의 전체 목록은 꽤 길다.

지금은 이런 예약어를 외워둘 필요는 없고 다만 뭔가가 예상대로 동작하지 않을 때 예약어를 사용한 게 혹시 문제가 될 수도 있다는 점만 기억하면 된다. 경험상 char(한 글자 문자열을 저장하는 용도로)와 class를 변수명으로 사용하는 실수를 가장 많이 저지른다.

연습문제 4

앞에 있는 두 연습문제의 풀이를 while 대신 for를 사용해 재작성하라.

풀이

result = 1
for counter in [0...10]
    result = result * 2
show result

여기서 마지막 범위를 포함시키지 않게 한 것과 순환문 내의 명령을 두 공백만큼 들여써서 각 줄이 위에 있는 줄에 속한다는 사실을 명확히 보여준 것에 주의하자(커피스크립트에서 요구하는 대로).

line = ''
for counter in [0...10]
    line = line + '#'
    show line

프로그램은 종종 이전 값을 기반으로 변수를 업데이트해야 한다. 예를 들어 counter = counter + 1처럼 말이다. 커피스크립트는 이를 위한 단축 구문을 제공한다. 바로 counter += 1이다. 이 기법은 다른 여러 연산에도 그대로 적용할 수 있다. 예를 들어 result의 값을 2배로 하려면 result *= 2를 사용하거나 counter -= 1를 사용해 counter 값을 1만큼 줄일 수 있다. counter++와 counter--는 각각 counter += 1 및 counter -= 1를 줄인 버전이다.


순환문은 프로그램의 제어 흐름에 영향을 준다고 말한다. 순환문은 명령이 실행되는 순서를 바꾼다. 순환문과 더불어 많은 경우 명령을 건너뛰는 흐름이 프로그램에 도움이 된다.

이번에는 3과 4로 모두 나눌 수 있는 0과 20 사이의 모든 숫자를 보여주려고 한다.

for counter in [0..20]
    if counter % 3 == 0 and counter % 4 == 0
        show counter

if 키워드는 while 키워드와 크게 다르지 않다. 이 키워드는 주어진 조건을 검사해 조건에 부합할 때만 명령을 실행한다. 하지만 if 키워드 명령을 한 번만 실행하므로 명령은 0번 또는 한 번 실행된다.

나머지 연산자(%)를 사용하면 한 숫자를 다른 숫자로 나눌 수 있는지 쉽게 확인할 수 있다. 한 숫자를 다른 숫자로 나눌 수 있다면 나눗셈의 나머지이자 나머지 연산자가 반환하는 값은 0이 된다.

0과 20 사이의 모든 숫자를 출력하되 4로 나눌 수 없는 숫자만 괄호로 감싸려면 다음과 같이 할 수 있다.

for counter in [0..20]
    if counter % 4 == 0
        show counter
    if counter % 4 != 0
        show '(' + counter + ')'

하지만 이렇게 하면 프로그램이 counter를 4로 나눌 수 있는지 두 번 판단해야 한다. if문 다음에 else 영역을 추가하면 같은 효과를 그대로 누릴 수 있다. else 명령은 if 조건이 false일 때만 실행된다.

for counter in [0..20]
    if counter % 4 == 0
        show counter
    else
        show '(' + counter + ')'

이 예제를 좀 더 확장해 이번에는 같은 숫자를 출력하되 15보다 큰 숫자 다음에는 별을 두 개 추가하고, 10보다 큰 숫자(이면서 15보다 작은 숫자)에는 별을 한 개, 그 외 나머지 숫자에는 별을 추가하지 않는다고 가정하자.

for counter in [0..20]
    if counter > 15
        show counter + '**'
    else if counter > 10
        show counter + '*'
    else
        show counter

이 예제는 if 구문을 연쇄적으로 적용하는 법을 보여준다. 이 경우 프로그램은 먼저 counter가 15보다 큰지 살펴본다. counter가 15보다 크면 두 개의 별을 출력하고 나머지 테스트를 건너뛴다. counter가 15보다 크지 않으면 계속해서 counter가 10보다 큰지 확인한다. counter가 10보다 크지 않을 때만 마지막 show 명령에 도달한다.

연습문제 5

prompt를 사용해 자기 자신에게 2+2의 값을 묻는 프로그램을 작성하라. 값이 ‘4’이면 show를 사용해 칭찬 어구를 출력한다. 값이 ‘3’이나 ‘5’이면 ‘Almost!’를 출력한다. 그 밖의 답을 할 때는 질책하는 어구를 출력한다. prompt 사용과 관련한 마법은 30페이지를 참고하자.

풀이

prompt 'You! What is the value of 2 + 2?', '',
    (answer) ->
    if answer == '4'
        show 'You must be a genius or something.'
    else if answer == '3' || answer == '5'
        show 'Almost!'
    else
        show 'You are an embarrassment.'

프로그램의 로직 검사는 복잡할 수 있다. 조건을 명확히 작성할 수 있게끔 커피스크립트는 if문의 변종을 한두 개 제공한다. if문의 본체는 조건 앞에 둘 수 있다. if not은 unless로 쓸 수 있다.

fun = on
show 'The show is on!' unless fun is off

순환문이 항상 끝까지 실행돼야 하는 것은 아니며, 때로는 break 키워드가 유용할 때가 있다. break 키워드는 현재 순환문에서 바로 빠져 나와 다음 명령을 계속 실행하게 해준다. 다음 프로그램은 20보다 크면서 7로 나눌 수 있는 첫 번째 숫자를 찾는 프로그램이다.

current = 20
loop
    if current % 7 == 0
        break
    current++
show current

loop 구문은 순환문의 종료 조건을 검사하는 영역을 포함하지 않는다. 이는 while true와도 같다. 이 말은 순환문 내의 break문에 의존해 순환문을 종료한다는 뜻이다. 같은 프로그램을 다음과 같이 좀 더 간단히 작성할 수도 있다.

current = 20
current++ until current % 7 == 0
show current

이 경우 순환문의 본체가 순환문 테스트보다 앞에 나온다. until 키워드는 unless 키워드와 유사하지만 while not이라는 의미로 해석한다. 이 순환문의 유일한 효과는 current를 원하는 값까지 증가시키는 것뿐이다. 하지만 여기서는 break를 사용하는 예제가 필요했으므로 첫 번째 버전도 주의 깊게 살펴보자.

연습문제 6

1부터 6 사이의 행운의 숫자를 고르고 행운의 숫자가 나올 때까지 시뮬레이션 주사위를 계속 굴린다. 주사위를 굴린 횟수를 센다. 순환문을 사용하고 선택적으로 break를 사용한다. 주사위를 던지는 시뮬레이션은 roll = Math.floor Math.random() * 6 + 1를 통해 할 수 있다.

참고로 loop은 while true와 같으며 둘 다 그 자체로는 끝나지 않는 순환문을 만드는 데 사용할 수 있다. while true는 유용한 기법이지만 순환문에게 true가 true인 동안 반복하라고 요청하는 식이므로 조금 바보 같다. 따라서 loop을 사용하는 방식을 더 권장한다.

풀이

luckyNumber = 5 # 1과 6 사이에서 고른다
show "Your lucky number is #{luckyNumber}"
count = 0
loop
    show roll = Math.floor Math.random() * 6 + 1
    count++
    if roll is luckyNumber then break
show "Luck took #{count} roll(s)"

또 다른 방법은 break를 사용하지 않고 푸는 방법으로 보기에 따라서는 더 좋을 수도 있다.

luckyNumber = 3 # 1과 6 사이에서 고른다
show 'Your lucky number is ' + luckyNumber
count = 0
until roll is luckyNumber
    show roll = Math.floor Math.random() * 6 + 1
    count++
show 'You are lucky ' + Math.floor(100/count) + '% of the time'

앞에 있는 연습문제의 두 번째 풀이에서 roll은 처음 순환문을 순회할 때는 값을 지정하지 않았다. 이 값은 다음 명령에서 값을 대입한다. 그런데 만일 이 변수 값을 가져오면 어떤 일이 일어날까?

show mysteryVariable
mysteryVariable = 'nothing'

촉수와 관련해 이 변수는 허공에서 값을 찾게 되고 아무 값도 집지 못한다. 빈 공간의 값을 요청하면 undened라는 이름의 특수 값을 받게 된다. 내장 함수인 console.log처럼 흥미로운 값을 반환하지 않는 함수도 undened 값을 반환한다. 커피스크립트에서는 대부분의 명령이 값을 반환한다. prelude 함수인 show는 주어진 값을 반환하므로 표현식에서 다음과 같이 사용할 수 있다.

show console.log 'I am a side effect.'

이와 유사한 값인 null도 있다. null은 ‘이 변수가 정의됐지만 값이 없다’는 의미를 나타낸다. undened와 null의 의미 차이는 대부분 학문적이며, 보통은 그다지 중요하지 않다.

실제 프로그램에서는 뭔가가 ‘값을 갖고 있는지’ 검사해야 할 때가 종종 있다. 이런 경우 something? 같은 표현식을 사용할 수 있다. ?는 존재 연산자라고 한다. 이 연산자는 뭔가가 null 또는 undened가 아닐 때 true를 반환한다. 이 연산자는 ?=처럼 존재 대입 연산자 형태로도 사용한다. 이 경우 변수가 null 또는 undened일 때만 값을 대입한다.

show iam ? undefined
iam ?= 'I want to be'
show iam
iam ?= 'I am already'
show iam if iam?

이제 자연스럽게 다음 주제를 살펴볼 차례다. 자바스크립트를 접한 경험이 있다면 각기 다른 타입의 값을 비교하기가 쉽지 않다는 점을 알고 있을 것이다.

show false == 0
show '' == 0
show '5' == 5

자바스크립트에서는 이들 결과 값이 모두 true다(커피스크립트에서는 모두 false다). 각기 다른 타입을 갖고 있는 값을 비교할 때는 타입을 호환 타입으로 먼저 변환해야 한다. 앞서 Number를 사용해 Number('5') == 5를 비교한 결과가 true인 것을 보면서 이를 살펴본 바 있다. 커피스크립트에서 ==의 동작 방식은 자바스크립트에서의 ===와 같다.

show `null === undefined `
show `false === 0`
show `'' === 0`
show `'5' === 5`

이들 결과는 모두 false다. 커피스크립트에서는 자바스크립트 코드를 역따옴표로 감싸서 포함시킬 수 있다. 커피스크립트에서 자바스크립트를 사용하는 것은 고수준 언어에서 어셈블리 언어를 사용하는 것과 유사하다. 즉 좀처럼 사용할 일이 거의 없다는 뜻이다.


때로는 자동 타입 변환을 초래하는 상황이 있다. 비문자열 값을 문자열에 더하면 값이 문자열로 합쳐지기 전에 문자열로 자동 변환된다. 숫자에 문자열을 곱하면 커피스크립트는 문자열을 숫자로 이해하려고 노력한다.

show 'Apollo' + 5
show null + 'ify'
show '5' * 5
show 'strawberry' * 5

마지막 명령은 NaN이라는 특수 값을 출력한다. NaN은 ‘not a number’의 약자로, 숫자 타입이다(조금 이상하게 들릴 수도 있지만). 이 경우 이 값은 strawberry가 숫자가 아니라는 사실을 나타낸다. NaN에 대한 모든 수학 연산은 NaN을 반환한다. 이 예제에서 5를 곱해도 NaN 값이 나오는 이유는 이 때문이다. 더불어 종종 헷갈릴 수 있는데, NaN == NaN의 비교 결과는 false다. 값이 NaN인지 확인할 때는 isNaN 함수를 사용해야 한다.

자동 변환은 매우 편리할 수 있지만 조금 이상하고 오류가 나기도 쉽다. +와 * 모두 수학 연산자이긴 하지만 이 예제에서는 전혀 다르게 동작한다. 필자는 코드에서 +를 비문자열에 많이 사용하지만 *나 다른 수학 연산자는 문자열 값에 사용하지 않는 것을 원칙으로 한다.

숫자를 문자열로 변환하는 일은 항상 가능하고 아주 간단하지만, 문자열을 숫자로 변환하는 일은 제대로 동작하지 않을 수 있다(이 예제의 마지막 줄처럼). 문자열을 숫자로 명시적으로 변환할 때는 Number를 사용할 수 있다. 이 경우 변환 결과로 인해 NaN 값을 얻을 수도 있다는 점을 분명히 인식해야 한다.

show Number('5') * 5

앞서 불리언 연산자 &&와 ||를 설명하면서 두 연산자가 불리언 값을 내놓는다고 설명한 바 있다. 사실 이는 지나치게 단순화한 설명이다. 두 연산자를 불리언 값에 적용하면 실제로 불리언 값이 반환된다. 하지만 이들 연산자를 다른 값에 적용하면 인자 값 중 하나가 반환된다.

||가 하는 일은 다음과 같다. ||는 먼저 왼쪽 값을 살펴본다. 이 값을 불리언 값으로 변환한 결과가 true이면 왼쪽 값을 반환하고, true가 아니면 오른쪽 값을 반환한다. 인자가 불리언일 때 이 연산자가 이 작업을 올바르게 하는지 직접 확인해 보자. 이 연산자는 왜 이런 식으로 작업할까? 사실 이는 매우 편리한 기능이다. 다음 예제를 살펴보자.

prompt 'What is your name?', '',
    (input) ->
        show 'Well hello ' + (input || 'dear')

사용자가 이름을 입력하지 않고 엔터를 누르면 input 변수는 '' 값을 갖는다. 이를 불리언으로 변환하면 false가 된다. 이 경우 input || 'dear' 표현식은 ‘변수 input의 값 또는 'dear' 문자열’이 된다. 이를 이용하면 값이 없을 때 대신 사용할 수 있는 예비 값을 쉽게 제공할 수 있다.

&& 연산자도 유사한 기능을 하지만 방향은 정반대다. 왼쪽의 값을 불리언으로 변환했을 때 값이 false이면 이 연산자는 왼쪽 값을 반환하고, 그렇지 않으면 오른쪽 값을 반환한다.

두 연산자의 또 다른 특징은 오른쪽에 있는 표현식은 필요할 때만 해석한다는 점이다. true || X의 경우 X가 무엇이든 결과는 true이므로 X는 절대 해석할 일이 없으며, X의 부수 효과도 절대 일어나지 않는다. false && X에도 같은 원칙이 적용된다.

false || alert 'I am happening!'
true || alert 'Not me.'