언제 필요한가요?
define과 let으로 값에 이름을 붙입니다. 프로시저가 다음 호출에서 바뀐 값을 읽게 하려면 set!을 쓰세요.
이름과 값의 연결을 바인딩이라고 부르며, 바인딩마다 값을 담는 셀이 하나씩 있습니다.
실행으로 확인하기
아래 프로그램 전체를 bindings.lspx로 저장하세요. 먼저 네이티브 CLI를 설치하거나, 각 예제를 플레이그라운드에서 따로 실행하세요.
(define x 1)
(define get (lambda () x))
(set! x 2)
(get)lispex run bindings.lspx출력
2명령의 종료 상태는 0이며 파일을 생성하지 않습니다.
예제 읽기
get은 호출할 때 x를 읽습니다. 그 전에 set!이 x를 1에서 2로 바꿨으므로 (get)은 2를 반환합니다.
바인딩은 이름과 값을 저장할 자리를 연결합니다. 이 저장 자리를 셀이라고 합니다. set!은 기존 셀에 담긴 값을 바꿉니다.
스코프는 이름을 사용할 수 있는 프로그램 안의 범위입니다. 리스펙스는 어휘 스코프를 사용합니다. 따라서 소스 구조에 따라 이름이 가리키는 바인딩이 정해집니다.
안쪽에서 같은 이름 쓰기
안쪽 이름은 바깥 이름을 가릴 수 있습니다. 첫 예제와 별도로 실행하세요.
(define x 1)
(define get (lambda () x))
(list (let ((x 10)) (set! x 11) x) (get))결과
(11 1)안쪽 x는 자기 셀을 갖습니다. 그 값을 11로 바꿔도 바깥 x는 1입니다. 이렇게 이름을 가리는 것을 섀도잉이라고 합니다. get은 프로시저를 만들 때 참조한 바깥 x를 계속 읽습니다.
앞서 만든 로컬 값 쓰기
(let* ((x 6)
(y (+ x 1)))
(* x y))관측 결과
42let*은 이름을 위에서부터 차례로 만듭니다. 따라서 (+ x 1)은 이미 값이 6인 x를 사용할 수 있습니다. 바인딩의 처음 값을 구하는 식을 초기식이라고 합니다.
로컬 프로시저가 자신을 호출하게 하기
(letrec ((f (lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
(f 5))관측 결과
120f는 5의 팩토리얼을 구합니다. letrec은 초기식을 계산하기 전에 셀을 모두 만듭니다. 따라서 프로시저 본문에서 자기 이름 f를 참조할 수 있습니다. 같은 letrec 안의 여러 프로시저가 서로 호출할 수도 있습니다.
값이 들어가기 전에 셀을 읽으면 E321이 납니다. 예를 들어 (letrec ((x x)) x)는 x를 너무 일찍 읽습니다. 초기화 전까지 리스펙스는 그 셀에 내부 표시를 둡니다. 이 표시는 리스펙스 프로그램에서 만들 수 있는 값이 아닙니다.
최상위 이름 다시 정의하기
(define x 1)
(define get (lambda () x))
(define x 3)
(get)결과
3최상위 정의는 프로시저나 로컬 바인딩 형식 밖에 있는 정의입니다. 여기서 같은 이름에 define을 다시 쓰면 기존 셀을 갱신합니다. 따라서 앞서 만든 get도 새 값을 읽습니다.
빠르게 고르기
| 형식 | 쓸 때 | 초기식이 보는 범위 |
|---|---|---|
define | 최상위 이름이나 이름 있는 프로시저 | 현재 최상위 바인딩 |
let | 서로 독립인 로컬 값 | 바깥 바인딩 |
let* | 앞선 로컬 값에 의존하는 로컬 값 | 바깥 바인딩과 이 let*에서 앞서 만든 바인딩 |
letrec | 자신이나 서로를 호출하는 로컬 프로시저 | 이 letrec의 모든 바인딩. 초기화 전 셀을 읽으면 E321 |
set! | 기존 셀 갱신 | set!을 쓴 위치에서 보이는 바인딩 |
흔한 오해
바인딩되지 않은 이름에 set!을 쓰면 E303입니다. 이때 새 바인딩이 만들어지는 일은 없습니다.
다음으로
프로시저 문서에서 직접 프로시저를 정의하고 호출하는 법을 확인하세요. 클로저 가이드에서는 두 카운터가 값을 공유하는 경우와 따로 보관하는 경우를 비교합니다.