J언어를 배워보자 - 제 1장. 기본기

Roger Stokes이 쓴 Learning J의 chapter 1:Basics를 번역/정리했다.

요즘 J를 배워보려 하는데 관련된 한국어 자료가 거의 없었다. 그래서 공부하는 겸 Learning J를 읽고 간단히 정리했다. 원문을 그대로 옮기진 않았고, 필요하면 표현을 바꿨다.

이 책은 J701을 기준으로 쓰였다. 따라서 www.jsoftware.com 에서 J701을 다운받아서 설치해 하나하나 실습해보면 좋다. J는 APL, K와 비슷한 언어로 리스트, 행렬등을 연산하기에 좋은 기호 중심의 언어이다. 따라서 수학, 통계, 논리적인 분석등에 쓰기에 좋다. 코드가 일반 알파벳이 아닌 특수문자가 많아 읽기가 난해하고 기존의 제어 흐름과는 다른 프로그래밍 스타일 때문에 배우기 어려워하는 사람이 많다. J에 대한 자세한 소개는 여기 에서 위의 글 몇 개를 읽으면 된다.

j-code

1.1 인터렉티브 환경

J의 개발환경은 사용자가 한 줄 입력하면, 그게 곧바로 해석되어 아랫줄에 출력된다. 사용자의 입력하는 것은 들여쓰기가 된 것으로 보이고, 아래 줄에 출력되는 결과는 들여쓰기가 없다.

   2+2
4

결과가 출력되고 나면 바로 아랫줄에 다시 커서가 깜빡이고, 다른 표현식을 입력할 수 있다.

1.2 산수

곱셈을 위한 심볼은 *(asterisk)이다.

   2*3
6

물론 아래와 같이 빈칸을 집어넣을 수도 있다.

   2 * 3
6

결과는 똑같다. 빈칸은 있어도 되고 없어도 된다. 가독성을 위해서 필요하면 넣는다.

나눗셈을 위한 심볼은 %(percent)이다.

   3 % 4
0.75

뺄셈을 위해서는 -를 쓴다.

   3 - 2
1

다음 예제는 음수에 대한 것이다. 음수는 숫자 앞에 _부호가 붙는다. 숫자와 _사이에는 빈칸이 있어선 안 된다. _는 산수 할 때 쓰는 연산자가 아니라 숫자의 표현 중 하나이다. 마치 부동소수점을 표현할 때 수 중간에 .을 찍는 것과 같다.

   2 - 3
_1

-또한 음수를 만들어내기는 한다. 하지만 음수를 '만들어낸다'고 한 것에 주의해야 한다. _는 숫자를 표현하는 방식 중 하나이지만 -는 연산자라는 뜻이다.

   - 3
_3

거듭제곱을 위해서는 ^(caret)을 쓴다. 2의 3 제곱을 계산하면 아래와 같다.

   2 ^ 3
8

그냥 제곱을 위한 연산자는 *:(asterisk colon)이다.

   *: 4
16

1.3 몇몇 용어들: 함수, 인자, 적용, 값

2 * 3을 한번 보자. 이건 두 인자에 곱하기 함수인 *가 적용되었다 라고 말할 수 있다. 왼쪽 인자는 2이고 오른쪽 인자는 3이다. 그리고 23은 각 인자의 값이다.

1.4 리스트

리스트는 1 2 3 4와 같이 쓸 수 있다. 숫자 사이에 빈칸이 들어있음에 주의하자. 리스트에 있는 각 숫자의 제곱을 알고 싶으면 다음과 같이 쓴다.

   *: 1 2 3 4
1 4 9 16

위 예제에서 "Square" 함수(*:)가 리스트의 각 아이템에 적용되었음을 알 수 있다. 리스트로 이루어진 두 개의 인자와 +같은 함수가 주어지면 그 함수는 두 리스트에서 서로 대응되는 아이템에 적용된다.

   1 2 3 + 10 20 30
11 22 33

만약 한 인자는 리스트이고 다른 인자는 숫자 하나 라면, 숫자 하나는 리스트의 각 아이템에 대응된다.

   1 + 10 20 30
11 21 31

   1 2 3 + 10
11 12 13

이런 식의 실험은 새로운 함수가 여러 인자 패턴에 어떻게 동작하는지 알아보는 데에 효과적이다.

예를 들어서 7을 2로 나누면, 몫이 3이고 나머지가 1이다. 나머지를 계산하는 J의 내장 함수는 |(vertical bar)이다. |의 이름은 "Residue"이다. 여러 패턴이 들어왔을 때 아래와 같은 결과가 나온다.

   2 | 0 1 2 3 4 5 6 7
0 1 0 1 0 1 0 1

   3 | 0 1 2 3 4 5 6 7
0 1 2 0 1 2 0 1

Residue 함수는 "mod"(modulo)함수와 같다. 다만 이건 2|7이라고 쓰고, mod 함수는 7mod2라고 쓰는게 다르다.

1.5 괄호

필요하다면 표현식은 괄호를 포함할 수 있다. 괄호 안의 표현식은 따로 계산된다.

   (2+1)*(2+2)
12

괄호는 항상 필요하지는 않다. 3*2+1이라는 표현식을 보자. 이 식이 (3*2)+1로 해석되어 7이 될까, 아니면 3*(2+1)이 되어 9가 될까?

   3 * 2 + 1
9

학교에서 수학을 배울 때에는 곱셈을 덧셈보다 연산을 먼저 한다고 배웠다. 이런 규칙이 있어서 괄호를 적게 쓸 수 있다.

J에는 그런 규칙이 없다. 먼저 따로 계산해야 할 부분이 필요하다면 괄호를 쓰면 된다. 하지만 괄호가 없다면 뒤부터(오른쪽부터) 계산된다. 다시 말하자면 자연스레 읽었을 때에 연산자의 오른쪽 인자부터 계산된다. 따라서 3*2+1의 경우에는 *의 오른쪽 인자인 2+1부터 계산된다. 다른 예제를 보자.

   1 + 3 % 4
1.75

여기서 %+보다 먼저 적용되는 것을 볼 수 있다. 즉 "제일 오른쪽 먼저" 연산이 시작되었다.

이 "제일 오른쪽 먼저" 규칙은 일반 수학의 "덧셈보다 곱셈 먼저"규칙하고는 다르다. 이 규칙이 별로 편하지 않다면 그냥 괄호를 쓰면 된다. 하지만 이 규칙을 사용하면 함수나 숫자가 엄청나게 많이 사용된 문장에서도 어디에서부터 계산해나가야 할지 쉽게 알 수 있다.

이 책에서는 "제일 오른쪽 먼저" 규칙을 적용 시킬 필요가 없다면 괄호를 쓸 것이다. 지금 여기서 중요한 건 코드를 잘 읽을 수 있을 정도로 표현식의 구조를 아는 것이다.

1.6 변수와 할당

영어 표현으로 'let x be 100'라는 문장은 J로

   x =: 100

로 나타낼 수 있다.

우리가 할당문이라고 불리는 이 표현식은 100이라는 값을 x라는 이름에 할당한다. 이 말은 즉 x라는 이름의 변수가 생성되고 이 변수에 100이라는 값을 넣었다고 할 수 있다. 만약 사용자가 입력한 한 라인에 할당문만 있다면 그 아래 결과가 출력되는 줄에는 아무런 값도 출력되지 않는다.

   x - 1
99

값이 할당된 변수는 다른 표현식에서 가져다 쓸 수 있다.

   y =: x - 1

여기서 yx-1의 결과 값을 저장하는 데에 쓰였다. 변수에 저장된 값을 살펴보고 싶다면 그냥 변수의 이름을 입력하면 된다. 그것만으로도 다른 표현식처럼 하나의 표현식이 된다.

   y
99

같은 변수에 반복해서 할당이 가능하다. 마지막에 할당한 것이 변수에 담긴다.

   z =: 6
   z =: 8
   z
8

변수는 그 변수에 할당된 값을 이용해서 자기 자신에게 새로운 값을 할당할 때에도 사용할 수 있다.

   z =: z + 1
   z
9

위의 예제는 한 라인이 할당문으로만 이루어졌을 때에 결과 줄에 아무것도 출력되지 않음을 보여준다. 그럼에도 할당문 역시 하나의 표현식이다. 할당문이 어떤 표현식의 부분에 쓰였을 때에는 값으로 사용할 수 있다.

   1 + (u =: 99)
100
   u
99

아래는 어떤 이름을 변수명으로 사용할 수 있는지에 대한 예제이다.

   x                       =: 0
   X                       =: 1
   K9                      =: 2
   finaltotal              =: 3
   FinalTotal              =: 4
   average_annual_rainfall =: 5

변수명은 반드시 알파벳으로 시작해야 한다. 그리고 변수명은 알파벳(대문자, 소문자), 숫자(0-9), 밑줄(_)로 이루어진다. 그리고 대소문자를 가린다. 즉, xX는 다른 변수이다.

   x
0
   X
1

1.7 용어: 모나드와 다이아드(Monads and Dyads)

오른쪽에 하나의 인자만 받는 함수를 모나딕 함수(monadic function)이라고 부른다. 줄여서 모나드라고 한다. 예를 들자면 "Square"(*:)함수가 있다. 함수의 오른쪽과 왼쪽에 인자를 하나씩 두어 총 두 개의 인자를 받는 함수를 다이아딕(dyadic) 함수라 부른다. 이 역시 줄여서 다이아드라고 한다. 위에서 봤던+가 그 예가 될 수 있겠다.

"Subtraction"(빼기) 함수와 "negation"(부정)함수는 같은 심볼(-)로 두가지 다른 기능을 하는 함수 중 하나이다. 다른 말로, -는 모나딕으로도 다이아딕으로도 사용할 수 있다는 것이다. 사실 J의 거의 모든 내장 함수가 모나딕과 다이아딕으로 사용될 수 있다. 아까 배웠던 나누기 함수, %도 그렇다. %는 다이아딕으로는 나누기이지만 모나딕으로 사용될 때는 역수 함수가 된다.

   % 4
0.25

1.8 내장 함수들 더 보기

이 섹션의 목적은 J의 내장 함수 중 몇 개를 더 소개해서 J로 프로그래밍하는 걸 맛 보는데 있다.

영어로 "add together the numbers 2, 3, and 4, or more"말을 살펴보자. 이건 더 간단하게 다음과 같이 표현할 수 있다.

add together 2 3 4

이걸 계산하면 9이다. 이걸 J로 표현하면 다음과 같다.

   + / 2 3 4
9

영어 표현과 J의 표현을 비교해보자. "add"는 +에 대응되고 "together"는 /에 대응된다. 비슷하게 다음 표현을 생각해보자.

multiply together 2 3 4

이걸 계산하면 24이다. 이건 J로 다음과 같이 표현한다.

   * / 2 3 4
24

+/2 3 42+3+4와 같은 의미이고 */2 3 42*3*4와 같은 의미임을 알 수 있다. /는 "Insert"라고 한다. 이 녀석은 왼쪽에 있는 함수를 오른쪽의 리스트의 각 아이템 사이에 끼워 넣는 역할을 하기 때문이다. 일반화하여 말하자면, F가 임의의 다이아딕 함수이고, L이 숫자로 이루어진 리스트 a, b, c, .... y, z 라면

F / L    은   a F b F .... F y F z   이다.

또 다른 함수를 더 알아보자. 아래에 세 가지 명제가 있다.

2 는 1보다 크다     (명백히 참이다)
2 는 1과 같다          (거짓)
2 는 1보다 작다        (거짓)

J에서 "참"은 1로 표현하고, "거짓"은 0으로 표현한다. 그래서 위의 세 명제는 다음과 같이 쓸 수 있다.

   2 > 1
1

   2 = 1
0

   2 < 1
0

만약 x가 수열이라면,

   x =: 5 4 1 9

'x의 어떤 수가 2보다 큰가?' 라고 J에게 물어볼 수 있다.

   x > 2
1 1 0 1

위에서 보면, '어떤 게 2보다 큰가?'라는 물음에 첫 번째, 두 번째, 네 번째가 1로 표현되었다. x에서 2보다 더 큰 숫자가 존재하는지 알려면 이렇게 쓰면 될까?

   * / x > 2
0

틀렸다. x>21 1 0 1인데 곱셈에(110*1) 0("거짓")이 들어있으면 죽어도 1이 될 수 없다. x에 2보다 큰 수가 얼마나 많은지 알려면 어떻게 해야 할까? x>2의 결과를 전부 더하면 된다!

   + / x > 2
3

x에 숫자가 몇 개 있는지 알려면? x=x의 결과를 모두 더하면 된다.

   x
5 4 1 9

   x = x
1 1 1 1

   +/ x = x
4

그런데 이미 리스트의 길이를 반환하는 #이라는 내장 함수가 있다.

   # x
4

1.9 옆으로 늘어 놓아 표시하기

우리가 컴퓨터에 J를 입력할 때, 입력한 문장과 결과 값은 아래로 주욱 내려가면서 붙어 표시된다. 아까 입력했던 몇 줄을 보면 알 수 있다.

   x
5 4 1 9
   x = x
1 1 1 1
   +/ x = x
4
   # x
4

이제부터는 종종 옆으로 늘어놓아서 표시할 것이다. 밑의 표처럼 말이다.

x x = x +/ x = x # x
5 4 1 9 1 1 1 1 4 4

이 표는 코드가 수행되는 순서에 따라서, x를 입력하면 5 4 1 9가 나오고, x=x를 입력하면 1 1 1 1이 나오고... 그런 의미이다. 이런 표시방식은 J에서 제공하는 것은 아니다. 하지만 여기에선 자주 쓸 거다. 첫째 줄에는 입력들이, 그 아래 줄에는 출력들이 나온다.

할당문(x=:something)을 입력했을 때에는 아무것도 표시되지 않은 것을 봤을 것이다. 그래도 할당문은 표현식이기 때문에 값을 가진다. 할당문의 값을 보거나, 뭔가를 기억하거나 보기에 쉽도록 종종 차례로 문장이 완성되는 것을 보여줄 거다. 예를 들자면 아래와 같다.

x =: 1 + 2 3 4 x = x +/ x = x # x
3 4 5 1 1 1 3 3

이제 다시 내장 함수로 돌아가자. 리스트가 하나 있다고 하자. 그럼 우리는 그 리스트를 차례대로 보면서 "통과, 통과, 아니, 통과, 아니" 하면서 특정 아이템들만 고를 수 있다. 리스트를 선택하기 위한 리스트(아까 말했던 "통과, 아니"의 리스트)는 J로는 1 1 0 1 0으로 표현할 수 있다. 이렇게 1과 0으로만 이루어진 리스트를 비트-스트링(bit-string)이라고 부른다. (비트-리스트나 비트-벡터라고 부르기도 한다.) #함수가 다이아딕으로 사용되면 왼쪽 인자로 들어온 비트-스트링을 이용해서 오른쪽 인자의 아이템을 골라서 새로운 리스트를 만든다.

y =: 6 7 8 9 10 1 1 0 1 0 # y
6 7 8 9 10 6 7 9

이제 y에서 특정한 조건을 만족하는 아이템만 고를 수도 있다. 예를 들자면 7보다 큰 수들 같은 것.

y y > 7 (y > 7) # y
6 7 8 9 10 0 0 1 1 1 8 9 10

1.10 주석

J에서 NB.심볼은 주석을 나타낸다. NB.심볼 부터 그 라인의 끝까지는 평가되지 않는다. 예를 들자면 아래와 같다.

   NB.   여기는 라인 전체가 주석입니다.

   6 + 6   NB. 12가 출력돼야 합니다.
12

1.11 내장 함수가 가진 이름의 구조

J의 각 내장 함수들은 공식적인 이름과 비공식 이름이 있다. 예를 들어 공식적인 이름이 +인 함수는 비공식 이름이 "Plus"이다. 게다가 모나딕과 다이아딕의 경우도 생각해야 한다. 공식 이름이 -인 함수는 비공식 이름이 "Negate"와 "Minus"가 있다. 비공식적인 이름은 보통 한 단어 정도로 짧다. 이 이름은 J가 인식하지 못하기때문에 J에서는 반드시 공식적인 이름을 사용해야 한다. 이 책에서는 비공식 이름은 큰따옴표로 감싸서 표현한다. ("Minus"처럼)

거의 모든 내장 함수가 한 글자 내지 두 글자로 이루어진 공식적인 이름을 가지고 있다. (**: 등등...) 두 번째 글자는 항당 :(colon)나 .(dot, full stop, period)이다.

두 글자로 이루어진 이름은 한 글자로 이루어진 함수와 뭔가 관련이 있다. "Square"(*:)는 "Times"(*)와 관련성이 있다. 느껴지는가?

따라서 J의 내장언어는 한 함수에 보통 6개 정도의 관련 함수들이 있다. 기본 함수에 더해서 위에서 말한 :.가 붙은 두 가지 변형과 각각에 대한 모나딕, 다이아딕까지 있다. 예제로 >패밀리를 보자.

다이아딕 '>'은 위에서 봤듯이 "Larger Than"이다.

모나딕 '>'은 뒤에 나올 것이다.

모나딕 '>.'은 올림 연산을 한다. 올림은 자기 자신보다 큰 정수 중 가장 가까운 수이다. 이것의 이름은 "Ceiling"이다.

   >. _1.7 1 1.7
_1 1 2

다이아딕 '>.'은 두 인자 중 큰 녀석을 선택한다. ("Larger Of")

   3 >. 1 3 5
3 3 5

/를 이용해서 "Larger Of"를 리스트의 아이템 사이에 끼워 넣으면, 리스트에서 가장 큰 수를 뽑아낼 수 있다. 예를 들어서 리스트 1 6 5에서 가장 큰 수를 알아내려면 >. / 1 6 5를 입력하면 된다. 아래 예제를 통해 어떻게 해서 6이 나오는지 확인할 수 있다. 주석은 왜 이전 입력과 같은 결과가 나오는지 이유를 설명한다.

   >. / 1 6 5
6
   1 >. 6 >. 5      NB. /의 의미에 따라서
6
   1 >. (6 >. 5)    NB. 제일 오른쪽 먼저 규칙에 따라서
6
   1 >. (6)         NB. >.의 의미에 따라서
6
   1 >. 6           NB. ()의 의미에 따라서
6
   6                NB. >.의 의미에 따라서
6

모나딕 '>:'은 비공식적으로 "Increment"라고 부른다. 인자의 값에 1을 더한다.

   >: _2 3 5 6.3
_1 4 6 7.3

다이아딕 '>:'은 "Larger or Equal"이다.

   3 >: 1 3 5
1 1 0

이 책의 1장을 마친다.