값을 기억하는 클로저

카운터 두 개를 만들고 각자 값을 보관할지 같은 값을 함께 바꿀지 정합니다.

카운터 하나를 두 번 호출하기

아래 프로그램 전체를 counter.lspx로 저장하세요. 먼저 네이티브 CLI를 설치하거나, 각 예제를 플레이그라운드에서 따로 실행하세요.

LISPEX
(define counter (let ((n 0)) (lambda () (set! n (+ n 1)) n)))
(list (counter) (counter))
SH
lispex run counter.lspx

출력

출력
(1 2)

명령의 종료 상태는 0이며 파일을 생성하지 않습니다.

let은 n의 처음 값을 0으로 정합니다. lambda는 n에 1을 더하고 새 값을 반환하는 프로시저를 만듭니다. counter는 그 프로시저의 이름입니다.

첫 호출은 n을 1로, 둘째 호출은 2로 바꿉니다. let이 끝난 뒤에도 프로시저는 n을 사용할 수 있습니다. 이렇게 바깥 코드의 이름을 계속 사용할 수 있는 프로시저를 클로저라고 합니다.

카운터마다 자기 값 보관하기

다음 프로그램을 separate-counters.lspx로 저장하세요.

LISPEX
(define (make-counter)
  (let ((n 0))
    (lambda ()
      (set! n (+ n 1))
      n)))

(define counter-a (make-counter))
(define counter-b (make-counter))
(list (counter-a) (counter-b) (counter-a) (counter-b))
SH
lispex run separate-counters.lspx

출력

출력
(1 1 2 2)

명령의 종료 상태는 0이며 파일을 생성하지 않습니다.

make-counter를 호출할 때마다 let을 실행해 별개의 n을 만듭니다. counter-a를 호출하면 자기 값만 바뀝니다. counter-b도 0에서 시작하지만 자기 값을 따로 올립니다.

두 카운터가 값 하나 공유하기

다음 프로그램을 shared-counters.lspx로 저장하세요.

LISPEX
(define counters
  (let ((n 0))
    (list (lambda () (set! n (+ n 1)) n)
          (lambda () (set! n (+ n 1)) n))))

(define counter-a (car counters))
(define counter-b (car (cdr counters)))
(list (counter-a) (counter-b) (counter-a) (counter-b))
SH
lispex run shared-counters.lspx

출력

출력
(1 2 3 4)

명령의 종료 상태는 0이며 파일을 생성하지 않습니다.

여기서는 let을 한 번 실행합니다. 하나의 n과 이를 함께 쓰는 프로시저 두 개를 만듭니다. car로 첫 프로시저를 고르고, cdr 뒤에 car를 써서 둘째 프로시저를 고릅니다.

호출은 왼쪽부터 실행합니다. counter-a가 n을 1로 바꾼 다음 counter-b가 같은 n을 2로 바꿉니다. 다음 두 호출은 3과 4를 반환합니다.

클로저가 기억하는 것

바인딩은 이름과 값을 저장할 자리를 연결합니다. 그 저장 자리를 셀이라고 합니다. 바깥 바인딩을 계속 사용할 수 있도록 참조를 보관하는 것을 캡처라고 합니다. 클로저는 셀을 캡처하므로 나중에 그 셀의 값이 바뀌면 새 값을 읽습니다.

프로그램n을 만드는 곳번갈아 호출한 결과
값을 따로 보관하는 카운터make-counter를 호출할 때마다 새 n(1 1 2 2)
값을 공유하는 카운터같은 let에서 두 프로시저가 쓸 n 하나(1 2 3 4)

두 프로그램 모두 let이 끝나면 반환한 프로시저를 통해 n에 접근합니다. 상태를 사용하는 프로시저 가까이에 값을 두세요. 코드를 바꿀 때는 위 예제처럼 연속된 호출 결과를 확인하세요.

안쪽 바인딩이 바깥 이름을 가리면 두 바인딩은 각자 셀을 갖습니다. set!은 소스 구조에 따라 가장 가까운 바인딩을 바꿉니다. 최상위에서 define을 반복하면 기존 전역 셀을 갱신하므로 앞서 만든 클로저도 새 값을 읽습니다. 두 경우의 예제는 바인딩 매뉴얼에서 볼 수 있습니다.

직접 확인하기

separate-counters.lspx의 (define counter-b (make-counter))를 (define counter-b counter-a)로 바꿔 보세요. 마지막 줄은 무엇을 반환할까요?

답

(1 2 3 4)를 반환합니다. 두 이름이 같은 프로시저를 가리키므로 하나의 n을 바꿉니다. 프로시저에 다른 이름을 붙여도 make-counter를 다시 실행하지는 않습니다.

다음으로

같은 실행 조건에서 프로그램을 새로 시작하고 같은 순서로 호출하면 같은 카운터 값을 얻습니다. 상태를 입력으로 전달할 수 있는 규칙은 결정 규칙 가이드에서 이어서 살펴보세요.

바인딩과 스코프 · 결정 기록