Scheme: 리스트

리스트

스킴의 원형인 리스프(LISP)는 "리스트 처리기(LISt Processor)"의 약자다. 리스트만 처리하는 건 아니지만 그만큼 리스트가 중요한 역할을 한다. 리스트는 여러 개의 값을 늘어놓은 것으로 list 함수로 만든다.

> (list 1 2 3)
(1 2 3)

간단히 괄호 앞에 작은 따옴표를 붙여도 된다.

> '(1 2 3)
(1 2 3)

apply 함수

apply는 리스트에 함수를 적용해주는 함수이다. 예를 들어 (apply + '(1 2 3))은 (+

1 2 3)과 같다.

> (apply + '(1 2 3)
6 

이것을 이용해서 리스트의 합계를 구하는 함수 sum을 만들어보자.

> (define (sum x) (apply + x))
> (sum '(1 2 3))
6 

apply처럼 함수를 다루는 함수를 고차 함수(high-order function) 또는 범함수(functional)라고 한다. 함수형 프로그래밍(functional programming)에서 functional을 함수의 형용사가 아니라 명사인 범함수로 해석하는 사람도 있다. 다시 말해 함수형 프로그래밍이란 apply와 같은 고차 함수가 중심이 되는 프로그래밍이라는 것이다.

length 함수

length 함수는 리스트의 길이를 돌려준다.

> (length '(1 2 3))
3

앞에서 정의한 sum 함수와 length 함수를 이용해 평균을 구하는 average 함수를 만들어보자.

> (define (average x) 
     (/
          (sum x)
          (length x)
     ))
> (average '(1 5 6)
4

car, cdr, cons

car는 리스트의 첫 원소, cdr는 리스트의 첫 원소를 제외한 나머지 원소를 돌려준다. cons는 리스트 앞에 원소를 덧붙여준다.

> (define x '(1 2 3))
> (car x)
1
> (cdr x)
(2 3)
> (cons 0 x)
(0 1 2 3)

car는 "카", cdr은 "카더"라고 읽는다. 1950년대에 리스프가 처음으로 구현된 IBM 704 기종의 기계어 명령에서 유래한 이름이라고 한다. 리스프에서 가장 거슬리는 함수명이지만 전통이라고 생각하자. 리스프에 영향을 받은 다른 언어들은 car, cdr 대신 head, tail이라는 함수명을 사용한다. 영 거슬린다면 이름을 새로 붙여주자.

> (define head car)
> (define tail cdr)

참고로 cadr은 리스트의 두번째 원소를 돌려주는 함수다. 아마도 car과 cdr을 합친 작명인 듯하다. 왜냐하면 리스트의 두번째 원소는 (car (cdr x))로 구할 수 있기 때문이다.

> (cadr '(1 2 3))
2
> (car (cdr '(1 2 3))
2

페어

페어(pair)는 두 개의 값을 짝지은 것이다. 다른 언어에서는 순서쌍 또는 튜플(tuple)이라고도 한다. 페어를 만드는 함수는 cons이다.

> (cons 1 2)
(1 . 2)

간단히 '(값 . 값) 형태로 쓸 수도 있다.

> '(1 . 2)
(1 . 2)

car은 페어의 첫 원소, cdr은 둘째 원소를 돌려준다.

> (car '(1 . 2))
1
> (cdr '(1 . 2))
2

cons, car, cdr의 쓰임새에서 알 수 있지만 사실 리스트는 페어에 바탕을 두고 만들어진 것이다. 즉 페어의 두 번 째 원소가 페어이고 그 두 번째 원소가 또 페어인 식이다.

> (cons 1 (cons 2 (cons 3 ())))
(1 2 3)
> '(1 . (2 . (3 . ())))
(1 2 3)

질문 하나. 그렇다면 리스트 (1 2)와 페어 (1 . 2)의 차이는 무엇일까? 리스트 (1 2)의 두 번 째 원소는 리스트 (2)이지만 페어 (1 . 2)의 두번째 원소는 값 2이다. 그리고 리스트 (2)는 2와 빈 리스트 ()의 페어 (2 . ())인 것이다.