변수와 유효범위: 이름과 맥락

리스펙스에서 변수는 단순한 저장소가 아니라, 추상화된 생각에 붙이는 이름표입니다. 유효범위는 그 이름이 어떤 맥락 안에서 의미를 갖는지 정의하는 보이지 않는 벽입니다.

이 가이드는 리스펙스의 입력 표면에서 바인딩을 만드는 방법과 렉시컬 스코프가 어떻게 작동하는지 설명합니다. 실행 의미는 범위 밖이며 표준 양식만 사용합니다.

최상위 바인딩: define

최상위에서는 define으로 식별자에 값을 바인딩합니다.

(define x 10)
(define y (+ x 5))      ; y ⇒ 15

예약어는 바인딩할 수 없습니다.

스코프 이해하기

리스펙스는 렉시컬 스코핑을 사용합니다: 프로그램 구조로 스코프가 결정됩니다.

기본 규칙: 코드는 변수를 찾을 때 “바깥쪽”은 볼 수 있지만 “안쪽”은 볼 수 없습니다.

변수를 사용할 때, 인터프리터는 다음 순서로 변수를 검색합니다:

  1. 현재, 가장 안쪽의 스코프.
  2. 찾지 못하면, 부모 스코프(현재 스코프를 포함하는 스코프)를 확인합니다.
  3. ... 이런 식으로 전역 스코프까지 계속 올라갑니다.
(define global-val 100)

(let ((outer 3))
  (let ((inner 4))
    (+ global-val (* outer inner))))      ; ⇒ 112
;; 여기서는 `inner`를 볼 수 없습니다 (전역만 보임)

새로운 스코프 만들기

일반적으로 let 계열과 함수 호출로 중첩 스코프를 만듭니다.

1. let으로 지역 바인딩하기

(let ...)은 본문 안에서만 유효한 지역 바인딩을 만듭니다.

(define x 10)

(let ((x 20) (y 5))
  (+ x y))                           ; ⇒ 25

x                                     ; ⇒ 10 (바깥 바인딩)

let*는 순차적으로 바인딩하고, letrec은 상호 재귀 지역 정의를 가능하게 합니다.

;; let* 순차 바인딩
(let* ((a 1)
       (b (+ a 2)))  ; a = 1을 볼 수 있음
  (+ a b))           ; ⇒ 4

;; letrec 지역 재귀
(letrec ((loop (lambda (xs acc)
                 (if (null? xs) acc
                     (loop (cdr xs) (+ acc (car xs)))))))
  (loop (list 1 2 3) 0))             ; ⇒ 6

2. 함수 호출

함수 호출은 파라미터와 지역을 위한 새 스코프를 만듭니다.

(define x 100)

(define (my-func param)
  (let ((local 10))
    (+ x param local)))

(my-func 50)                         ; ⇒ 160

기존 바인딩을 변경할 때는 set!을 사용합니다:

(define n 1)
(set! n (+ n 1))                      ; n ⇒ 2

함수는 정의된 환경의 바인딩을 볼 수 있습니다(렉시컬 스코프). 자세한 내용은 함수와 클로저 문서를 참고하세요.