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

등록일: 2014. 11. 05

컴퓨터 세계에는 데이터만 존재한다. 즉 데이터가 아닌 것은 존재하지 않는다. 모든 데이터가 본질적으로 비트의 연속[^1]으로 구성되고 근본적으로 같지만 데이터는 각기 다른 역할을 수행한다. 커피스크립트 시스템에서 대부분의 데이터는 값으로 분류된다. 모든 값은 각자 타입이 있으며, 이런 타입은 해당 값이 수행하는 역할을 결정한다. 값의 기본 타입은 숫자, 문자열, 불리언, 객체, 함수, undefined로 총 6개가 있다.

값을 생성하려면 값의 이름만 호출하면 된다. 이 과정은 아주 단순하다. 값을 만드는 데 필요한 재료를 모을 필요도 없고 돈을 지불할 필요도 없다. 그냥 값을 호출하기만 하면 값이 생긴다. 물론 허공에서 그냥 값이 생기지는 않는다. 값은 어딘가에 저장돼야 하며, 만일 엄청난 양의 값을 동시에 사용한다면 결국 컴퓨터 메모리가 바닥나게 된다. 다행히 이 문제는 값을 동시에 필요로 할 때만 생긴다. 값을 더 이상 사용하지 않으면 몇 비트만 남기고 값이 소멸된다. 이런 비트는 다음 번에 값을 생성하는 데 재활용된다.


예상한 독자도 있겠지만 숫자 타입의 값은 숫자 값이다. 이 값은 평상시에 숫자를 쓸 때처럼 쓴다.

144

이를 콘솔에 입력하면 출력 창에 같은 숫자가 출력된다. 여러분이 입력한 텍스트는 숫자 값을 생성하고 콘솔은 이 숫자를 받아서 화면에 다시 출력한다. 이 예제는 별로 쓸데없는 예제에 가깝지만 이 책에서는 앞으로 좀 더 복잡한 형태로 값을 생성하고 그 값이 콘솔에 어떻게 출력되는지 살펴볼 것이다.

다음은 144를 비트[^2]로 표현한 것이다.

01000000 01100010 00000000 00000000 00000000 00000000 00000000 00000000

위의 숫자는 64비트를 갖고 있다. 커피스크립트에서 숫자는 항상 64비트를 갖는다. 여기에는 중요한 의미가 내포돼 있다. 즉, 표현할 수 있는 숫자의 양이 제한적이라는 것이다. 세 개의 10진수로는 0부터 999까지의 숫자를 쓸 수 있으며, 이는 곧 103 = 1000개의 각기 다른 숫자를 뜻한다. 64개의 바이너리 숫자를 가지고는 264개만큼의 숫자를 쓸 수 있다. 이 숫자는 많은 숫자로, 1019보다 많다(숫자의 0이 19개).

하지만 1019 이하의 모든 정수를 커피스크립트로 표현할 수 있는 것은 아니다. 숫자에는 음수도 있으므로 비트 중 하나는 숫자의 부호를 저장하는 데 사용해야 한다. 이보다 더 큰 문제는 정수가 아닌 숫자도 표현해야 한다는 점이다. 이를 위해 숫자 내 십진수 점의 위치를 저장하는 데 11비트가 사용된다.

이로써 52비트만 남게 된다.[^3] 252(이 숫자는 1015보다 크다)보다 작은 정수는 커피스크립트 숫자로 안전하게 표현할 수 있다. 대부분의 경우 우리가 사용하는 숫자는 이보다 훨씬 작은 값이므로 비트에 대해 신경 쓸 일은 거의 없다. 작업을 하려면 이런 비트가 수없이 많이 필요하다. 하지만 이런 비트를 직접 사용하기보다는 큰 단위를 사용하는 게 작업하기에는 훨씬 간편하다.

분수는 점을 사용해 표현한다.

9.81

매우 큰 숫자나 매우 작은 숫자의 경우 e를 추가하고 숫자의 지수를 덧붙이는 과학적 표기법을 사용할 수도 있다.

2.998e8

이 값은 2.998 · 108 = 299 800 000이다.

52 비트 이내에 속하는 정수의 계산은 항상 정확하다. 하지만 아쉽게도 분수 계산은 일반적으로 정확하지 않다. π(파이)를 유한한 10진수로 정확히 표현할 수 없는 것처럼 64비트만으로 숫자를 저장할 수 있을 때는 많은 숫자가 일부 정확도를 잃어버린다. 이는 부끄러운 일이지만 매우 특정한 상황에서만 실제로 문제를 일으킨다.[^4] 중요한 점은 이런 사실을 인지하고 분수를 정확한 값이 아니라 근사치로 다뤄야 한다는 것이다.


숫자로 주로 하는 작업은 산수다. 덧셈과 곱셈 같은 산수 연산은 두 값을 받아 새로운 값을 생성한다. 다음은 커피스크립트에서 이런 산수 연산을 수행하는 코드다.

100 + 4 * 11

+와 * 기호는 연산자라고 한다. 첫 번째 기호는 덧셈을 나타내고 두 번째 기호는 곱셈을 나타낸다. 연산자 사이에 두 값을 집어넣으면 값에 연산자를 적용해 새 값을 도출한다.

그런데 이 예제는 ‘4와 100을 더하고 결과에 11을 곱하라’일까, 아니면 더하기 전에 곱셈을 적용하라는 의미일까? 이미 예상한 독자도 있겠지만 이때는 곱셈이 먼저 일어난다. 하지만 실제 수학 계산과 마찬가지로 괄호로 다음과 같이 덧셈 영역을 감싸면 순서를 바꿀 수 있다.

(100 + 4) * 11

뺄셈에는 - 연산자를 사용하고, 나눗셈에는 /를 사용한다. 괄호 없이 연산자를 사용하면 연산자의 우선순위에 따라 연산자의 적용 순서를 판단한다. 첫 번째 예제는 곱셈 연산자가 덧셈 연산자보다 우선순위가 높음을 보여준다. 나눗셈과 곱셈은 항상 뺄셈과 덧셈보다 먼저 수행된다. 우선순위가 같은 숫자 연산자가 나란히 나오면 (1 - 1 + 1)의 왼쪽에서 오른쪽 순서로 연산자가 적용된다. 다음 수식의 결과 값을 미리 예측해 보고 예상한 결과가 맞는지 실행해보자.

115 * 4 - 4 + 88 / 2

이런 우선순위 규칙은 걱정할 사항은 아니다. 연산자의 적용 순서가 의심된다면 그냥 괄호를 넣으면 된다.

독자들에게 조금은 낯설 수 있는 수학 연산자가 하나 더 있다. % 기호는 나머지 연산을 나타내는 데 사용한다. X % Y는 X를 Y로 나눈 나머지다. 예를 들어 314 % 100는 14이고, 10 % 3은 1이며, 144 % 12는 0이다. % 연산자는 곱셈 및 나눗셈 연산자와 동일한 우선순위를 갖는다.


다음으로 살펴볼 데이터 타입은 문자열이다. 문자열은 숫자와 달리 이름만으로는 그 용도를 알기가 쉽지 않지만 아주 기본적인 역할을 수행한다. 문자열은 텍스트를 나타내는 데 사용하며, 아마도 문자열이란 이름은 여러 문자를 서로 연결한다는 데서 유래한 것으로 보인다. 문자열은 내용을 따옴표로 감싸서 작성한다.

'Patch my boat with chewing gum.'

따옴표 안에는 거의 모든 내용을 집어넣을 수 있으며, 커피스크립트는 이를 통해 문자열 값을 만든다. 하지만 일부 글자는 읽기가 까다롭다. 따옴표 사이에 다른 따옴표를 집어넣으면 그만큼 읽는 게 어렵다.

'The programmer pondered: "0x2b or not 0x2b"'

커피스크립트는 작은따옴표와 큰따옴표 문자열을 모두 구현하며, 이는 문자열에 한 종류의 인용구만 있을 때 편리하다.

"Aha! It's 43 if I'm not a bit off"

큰따옴표로 감싼 문자열은 #{와 } 사이에 커피스크립트 코드 조각을 포함할 수 있다. 이렇게 삽입한 코드는 값을 먼저 해석한 후 문자열에 삽입된다.

"2 + 2 gives #{2 + 2}"

엔터를 입력할 때 생기는 새 줄은 일반적인 형태의 문자열 인용구 안에 집어넣을 수 없다. 문자열은 프로그램에서 줄이 길어지지 않게끔 여러 줄에 걸칠 수 있지만, 줄바꿈 표시는 출력 결과에 보이지 않는다.

'Imagine if this was a
very long line of text'

커피스크립트는 출력 결과에 줄바꿈을 그대로 유지해 여러 줄에 걸친 문자열을 쉽게 표현할 수 있는 삼중 인용 문자열을 지원한다. 인용 부호 앞에 있는 들여쓰기는 무시되므로 다음 줄은 보기 좋게 정렬된다.

'''First comes A
then comes B'''

삼중 큰따옴표 인용 방식에서는 보간 값도 사용할 수 있다.

"""  1
   + 1
   ---
     #{1 + 1}"""

문자열 안에 특수 기호를 사용하려면 다음과 같은 방법을 이용한다. 인용 텍스트에서 역슬래시(‘ \’)가 들어 있으면 이는 다음에 나오는 문자에 특별한 의미가 있음을 나타낸다. 역슬래시 앞에 나오는 인용 부호는 문자열을 끝내지 않고 문자열의 일부가 된다. ‘n’이라는 글자가 역슬래시 다음에 나오면 새 줄로 해석한다. 마찬가지로 ‘t’ 문자가 역슬래시 다음에 나오면 탭 문자로 해석한다.

'This is the first line\nAnd this is the second'

물론 때로는 특수 코드가 아니라 말 그대로 역슬래시를 문자열에 사용해야 할 때가 있다. 두 개의 역슬래시가 나란히 나오면 두 역슬래시가 하나로 합쳐져 결과 문자열 값에 한 개의 역슬래시만 남는다.

'A newline character is written like \"\\n\".'

문자열은 나누고, 곱하고, 뺄 수 없다. 하지만 + 연산자는 문자열에도 사용할 수 있다. 이때는 문자열을 더하는 게 아니라 연결하며, 두 개의 문자열을 이어준다.

'con' + 'cat' + 'e' + 'nate'

문자열을 조작하는 방법은 다양하며, 자세한 방법은 나중에 설명한다.


모든 연산자가 기호는 아니며 일부 연산자는 단어로 돼 있다. 예를 들어 지정한 값의 타입 이름을 문자열로 나타내는 typeof 연산자가 있다.

typeof 4.5

앞에서 본 다른 연산자는 모두 두 값에 적용되지만 typeof 연산자는 한 개의 값만 받는다. 두 값을 사용하는 연산자는 이항 연산자라고 하며, 한 개의 값만 사용하는 연산자는 단항 연산자라고 한다. - 연산자는 이항 연산자와 단항 연산자[^5] 모두로 사용할 수 있다.

-(10 - 2)

또 불리언 타입의 값도 있다. 이 값은 true 또는 false 값이다. 커피스크립트는 이 값에 별칭을 사용한다. true는 yes 또는 on, false는 no 또는 o로 쓸 수 있다. 이런 별칭을 사용하면 종종 프로그램을 읽기가 더 쉬워진다. 다음은 true 값을 생성하는 방법이다.

3 > 2

false 값은 다음과 같이 생성할 수 있다.

3 < 2

>와 < 기호는 전에 이미 본 적이 있을 것이다. 이들 기호는 각각 ‘~보다 큰’과 ‘~보다 작은’을 나타낸다. 이들 기호는 이항 연산자이며, 적용 결과로 불리언 값을 반환한다. 값이 특정 범위 내에 있는지 판단할 때는 이런 연산을 여러 개 사용할 수도 있다. 다음 비교는 각각 true와 false를 결과로 내놓는다.

100 < 115 < 200
100 < 315 < 200

문자열도 같은 방식으로 비교할 수 있다.

'Aardvark' < 'Zoroaster'

문자열의 비교는 다소 알파벳순에 가깝다. 대문자는 소문자보다 항상 ‘작다’. 따라서 'Z' < 'a'는 항상 true다. 알파벳에 속하지 않는 문자(‘!’, ‘@’ 등)도 순서에 포함된다. 실제 비교가 이뤄지는 방식은 유니코드 표준을 기반으로 한다. 이 표준에서는 그리스어, 아랍어, 일본어, 타밀어에 이르기까지 거의 모든 문자에 숫자를 지정한다. 이런 숫자를 갖고 있으면 컴퓨터 내에 문자열을 저장하는 데 유용하다. 이때는 숫자 목록을 가지고 문자열을 나타낼 수 있다. 문자열을 비교할 때 커피스크립트는 문자열 내의 각 글자의 숫자를 왼쪽에서 오른쪽 순서로 비교한다.

다른 유사 연산자로는 >= (‘~보다 크거나 같은’), <= (‘~보다 작거나 같은’), == (‘~와 같은’), != (‘~와 같지 않은’)이 있다. ==은 is로 쓸 수 있고 !=는 isnt로도 쓸 수 있다.

'Itchy' isnt 'Scratchy'

불리언 값 자체에 적용할 수 있는 유용한 연산자도 있다. 커피스크립트는 and, or, not이라는 세 가지 논리 연산자를 지원한다. 이들 연산자는 불리언의 논리적 조건을 판단할 때 사용할 수 있다.

논리 and 연산자는 &&로도 쓸 수 있다. 이 연산자는 이항 연산자이며, 결과는 두 값이 모두 true일 때만 true다.

true and false

논리 or 연산자는 ||로도 쓸 수 있으며 지정한 값 중 하나가 true이면 true다.

true or false

not은 느낌표인 !로도 쓸 수 있으며 특정 값을 역으로 적용하는 단항 연산자다. !true는 false이며 not false는 true다.

연습문제 1

((4 >= 6) || ('grass' != 'green')) && !(((12 * 2) == 144) && true)

이 값은 true일까? 이 코드 안에는 불필요한 괄호가 많아서 코드를 읽기가 어렵다. 이 코드를 좀 더 간단하게 표현하면 다음과 같다.

(4 >= 6 or 'grass' isnt 'green') and not(12 * 2 is 144 and true)

풀이

결과는 true다. 이 결과는 다음과 같이 단계적으로 줄일 수 있다.

(false or true) and not(false and true) true and not false true

여기서 'grass' != 'green'가 true라는 사실을 이해하기 바란다. 풀이 녹색이 될 수는 있지만 grass와 green은 같지 않다.

괄호가 필요한 시점이 매번 명확하지는 않다. 실제로 우리는 지금까지 살펴본 연산자에 대한 지식을 바탕으로 or가 가장 우선순위가 낮고 그다음이 and이며, 이어서 비교 연산자(>, == 등), 그 외 나머지가 나온다는 사실만으로도 충분히 연산 순서를 알 수 있다. 이러한 연산자의 우선순위는 괄호를 사용하지 않아도 간단히 사용할 수 있게끔 지정됐다.


지금까지 살펴본 예제에서는 모두 휴대용 계산기로 계산할 수 있을 만한 값을 사용했다. 즉, 값을 만들고 연산자를 적용해 새로운 값을 도출하는 게 전부였다. 이와 같은 값 생성은 모든 커피스크립트 프로그램에서 핵심적인 부분에 속하지만 일부분일 뿐이다. 값을 생성하는 코드는 표현식이라고 한다. 직접 쓴 모든 값(예를 들어 22 또는 'psychoanalysis')은 표현식이다. 괄호 사이의 표현식도 표현식이다. 두 표현식에 적용한 이항 연산자 또는 한 표현식에 적용한 단항 연산자도 표현식이다.

표현식을 개발하는 방법에는 몇 가지가 있지만 자세한 내용은 이 책의 내용을 좀 더 진행한 후 설명한다.

프로그램에는 표현식보다 큰 단위가 있다. 이를 명령이라고 한다. 프로그램은 이런 명령의 목록으로 개발한다. 한 명령이 여러 줄에 걸쳐 있을 수도 있지만 대부분의 명령은 한 줄로 끝난다. 명령은 세미콜론(;)으로 끝낼 수도 있다. 커피스크립트의 세미콜론은 한 줄에 여러 명령을 집어넣을 때 주로 사용한다. 명령의 가장 간단한 형태는 표현식 다음에 세미콜론을 집어넣는 형태다. 다음은 이를 사용한 프로그램이다.

1; !false

물론 이 프로그램은 쓸모가 없다. 표현식은 값을 생성할 수만 있으면 충분하지만 명령은 어떤 식으로든 세상을 바꿀 수 있어야 한다. 명령은 화면에 뭔가를 출력(이를 통해 세상을 바꾼다고 가정하자)하거나 뒤에 나오는 명령에 영향을 주게끔 프로그램의 내부 상태를 바꿀 수 있다. 이러한 변경 사항을 ‘부수 효과’라고 한다. 이 예제의 명령은 1과 true 값을 생성해 비트 버킷[^6]에 집어넣는 일만 한다. 이는 세상에 아무런 인상도 남기지 않으며, 아무런 부수 효과도 없다.


프로그램은 어떻게 내부 상태를 유지할까? 어떻게 사물을 기억할까? 앞에서는 기존 값에서 새 값을 생성하는 법을 살펴봤지만 이렇게 하더라도 기존 값이 바뀌지는 않으며, 새 값은 바로 사용하지 않으면 소멸된다. 값을 보관하기 위해 커피스크립트에서는 변수를 제공한다.

caught = 5 * 5

변수는 항상 이름을 갖고 있으며, 아무 값이나 가리켜 보관할 수 있다. 위의 명령은 caught라는 변수를 생성하고 이 변수를 사용해 5 곱하기 5의 결과를 저장한다.

위의 프로그램을 실행하고 나면 콘솔에 caught란 단어를 입력해 25라는 값을 확인할 수 있다. 변수 값을 가져올 때는 변수명을 사용한다. caught + 1도 사용할 수 있다. 변수명은 표현식으로 사용할 수 있으며, 더 큰 표현식의 일부로 사용할 수 있다.

변수에 새 값을 대입할 때는 = 연산자를 사용하며 이 경우 새 변수가 만들어진다. 변수명으로는 거의 모든 단어를 사용할 수 있지만 공백을 포함할 수는 없다. 숫자는 변수명의 일부가 될 수 있으며, catch22는 유효한 변수명이다. 하지만 변수명은 숫자로 시작할 수 없다. ‘$’와 ‘’는 글자와 마찬가지로 변수명에 사용할 수 있으며 따라서 $$라는 변수명도 유효하다.

변수가 값을 가리키더라도 이 변수가 해당 값에 영원히 묶여 있는 것은 아니다. 아무 때나 = 연산자를 사용해 기존 변수가 현재 값 대신 새 값을 가리키게 할 수 있다.

caught = 4 * 4

변수는 상자보다는 촉수에 가깝다. 변수는 값을 담기보다는 값을 쥐고 있다. 따라서 두 개의 변수가 같은 값을 참조할 수도 있다. 변수를 통해서는 프로그램이 현재 갖고 있는 값에만 접근할 수 있다. 뭔가를 기억해야 한다면 이를 잡을 수 있게 촉수를 키우거나 기존 촉수 중 하나를 사용해 새 값을 잡아야 한다. 루이지(Luigi)가 여러분에게 빚 진 돈을 기억하려면 다음과 같이 하면 된다.

luigiDebt = 140

그런 다음 루이지가 매번 조금씩 돈을 상환하면 변수에 새 숫자를 대입해 값을 조금씩 빼면 된다.

luigiDebt = luigiDebt - 35

특정 시점에 존재하는 변수 모음과 그 값을 환경이라고 한다. 프로그램이 시작하면 이 환경이 빈(empty) 환경으로 설정된다. 이 안에는 항상 여러 표준 변수가 들어 있다. 커피스크립트 프로그램을 실행하기 위해 coffee를 사용하거나 coffee -r ./prelude를 사용해 인터랙티브 환경을 실행할 때의 환경은 global이라고 한다.

이 환경은 →|/ ‘Tab’을 입력해 볼 수 있다. 브라우저가 페이지를 로드하면 브라우저는 window라는 새 환경을 생성하고 표준 변수를 환경에 추가한다. 이 페이지에서 프로그램을 통해 생성되고 수정된 변수는 브라우저가 새 페이지로 이동할 때까지 남는다.

[^1]: 비트는 보통 0과 1로 설명하는 두 개의 값으로 이뤄진 모든 형태를 일컫는다. 컴퓨터에서는 비트가 고전하량과 저전하량, 강신호와 약신호, CD의 반짝이는 부분과 밋밋한 부분 같은 형태를 띤다.

[^2]: 여기서 10010000 같은 값을 예상한 독자도 있을 것이다. 이어지는 내용을 계속해서 읽어보자. 커피스크립트의 숫자는 정수로 저장되지 않는다.

[^3]: 한 비트를 자유롭게 사용할 수 있는 기법으로 인해 실제로는 53이다. 자세한 내용이 궁금하다면 ‘IEEE 754’ 형식을 참고하자.

[^4]: 일례로 p = 1/3이면 6*p는 2다. 하지만 p+p+p+p+p+p는 매번 더할 때마다 반올림 오차가 증가하므로 더한 값이 2가 아니다. 이는 서문에서 보여준 for 순환문에서도 일어난다. 이 문제는 부동 소수 근사치에서 일반적으로 발생하는 문제이며, 커피스크립트의 버그가 아니다. 이를 처리하는 방법 중 하나는 숫자가 정확히 일치하는지 비교하는 대신 근사치 범위 내에 있는지 비교하는 것이다.

[^5]: 단항 - 연산자와 값 사이에는 공백이 없다는 점에 주의하자.

[^6]: 비트 버킷은 오래된 비트를 보관하는 장소를 말한다. 일부 시스템에서는 프로그래머가 이따금씩 이를 직접 비워줘야 한다. 다행히 커피스크립트는 자동 비트 재활용 시스템을 갖추고 있다.