파라미터

파라미터 (Parameters)

파라미터(parameter)는 전역 변수처럼 보이지만 동적 바인딩(dynamic binding)이 가능해서, 특정 코드 범위 안에서만 값을 바꿔 쓰고 벗어나면 원래 값이 자동으로 복원되는 메커니즘이에요. 스레드와 연속(continuation) 친화적으로 동작하기 때문에, 전역 상태를 다룰 때 아주 유용해요.

출처: Racket Reference

본문

Racket Guide의 Dynamic Binding: parameterize에서 파라미터를 소개해요.

파라미터 모델에 대한 기본 정보는 Parameters를 참고하세요. 파라미터는 Scsh [Gasbichler02]의 보존 스레드 유체(preserved thread fluid)에 해당해요.

스레드와 연속 친화적인 방식으로 코드를 파라미터화하려면 parameterize를 사용해요. parameterize 폼은 본문 표현식의 동적 범위(dynamic extent)에 대해 새로운 스레드 셀(thread cell)을 도입해요.

새 스레드를 만들 때, 새 스레드의 초기 연속에 대한 파라미터화는 생성 스레드의 파라미터화예요. 각 파라미터의 스레드 셀이 보존되므로 새 스레드는 생성 스레드의 파라미터 값을 "상속"해요. 연속이 한 스레드에서 다른 스레드로 옮겨지면 parameterize로 도입된 설정들이 연속과 함께 효과적으로 이동해요.

대조적으로, 파라미터에 직접 대입하는(파라미터 프로시저를 값으로 호출하는) 것은 스레드 셀의 값을 바꾸므로, 현재 스레드에 대해서만 설정을 바꿔요. 결과적으로 메모리 관리자 입장에서 parameterize를 통해 파라미터에 원래 연관된 값은, 파라미터가 변형되더라도 연속이 도달 가능한 한 계속 도달 가능하게 유지돼요.

procedure

(make-parameter v [guard name realm]) → parameter?
  v : any/c
  guard : (or/c (any/c . -> . any) #f) = #f
  name : symbol? = 'parameter-procedure
  realm : symbol? = 'racket

새 파라미터 프로시저를 돌려줘요. 파라미터의 값은 모든 스레드에서 v로 초기화돼요.

guard#f가 아니면 파라미터의 가드 프로시저로 사용돼요. 가드 프로시저는 한 인자를 받아요. 파라미터 프로시저가 인자에 적용될 때마다, 그 인자는 가드 프로시저로 전달돼요. 가드 프로시저가 돌려준 결과가 새 파라미터 값으로 사용돼요. 가드 프로시저는 파라미터 값의 변경을 거부하기 위해 예외를 발생시킬 수 있어요. 가드는 초기 값 v에는 적용되지 않아요.

name 인자는 object-name이 보고하는 파라미터 프로시저의 이름으로, realmprocedure-realm이 보고하는 영역으로 사용돼요.

base 패키지의 7.4.0.6 버전에서 name 인자를 추가했어요. base 패키지의 8.4.0.2 버전에서 realm 인자를 추가했어요.

syntax

(parameterize ([parameter-expr value-expr] ...)
  body ...+)
   parameter-expr : parameter?

Racket Guide의 Dynamic Binding: parameterize에서 parameterize를 소개해요.

parameterize 표현식의 결과는 마지막 본문의 결과예요. parameter-exprs는 설정할 파라미터를 결정하고, value-exprs는 bodys를 평가하는 동안 설치할 해당 값들을 결정해요. parameter-exprs와 value-exprs는 왼쪽에서 오른쪽으로(교차하여) 평가된 다음, 파라미터들은 value-exprs의 값을 담은 보존 스레드 셀들에 연속에서 바인딩돼요. 각 parameter-expr의 결과는 바인딩되기 직전에 parameter?로 검사돼요. 마지막 본문은 전체 parameterize 폼에 대해 꼬리 위치에 있어요.

parameterize 표현식의 동적 범위 밖에서는 파라미터들이 다른 스레드 셀에 바인딩된 채로 남아 있어요. 따라서 제어가 parameterize 표현식을 벗어나면 효율적으로 이전 파라미터 설정이 복원돼요.

parameterize 평가 중에 연속이 캡처되면, 그 연속을 호출하는 것은 효과적으로 파라미터화를 다시 도입해요. 파라미터화는 개인 키를 사용해 연속 마크(continuation mark)를 통해 연속에 연관되기 때문이에요(Continuation Marks 참고).

Examples:

> (parameterize ([exit-handler (lambda (x) 'no-exit)])
    (exit))
> (define p1 (make-parameter 1))
> (define p2 (make-parameter 2))
> (parameterize ([p1 3]
                 [p2 (p1)])
    (cons (p1) (p2)))
'(3 . 1)
> (let ([k (let/cc out
             (parameterize ([p1 2])
               (p1 3)
               (cons (let/cc k
                       (out k))
                     (p1))))])
    (if (procedure? k)
        (k (p1))
        k))
'(1 . 3)
> (define ch (make-channel))
> (parameterize ([p1 0])
    (thread (lambda ()
              (channel-put ch (cons (p1) (p2))))))
#<thread>
> (channel-get ch)
'(0 . 2)
> (define k-ch (make-channel))
> (define (send-k)
    (parameterize ([p1 0])
      (thread (lambda ()
                (let/ec esc
                  (channel-put ch
                               ((let/cc k
                                  (channel-put k-ch k)
                                  (esc)))))))))
> (send-k)
#<thread>
> (thread (lambda () ((channel-get k-ch)
                      (let ([v (p1)])
                        (lambda () v)))))
#<thread>
> (channel-get ch)
1
> (send-k)
#<thread>
> (thread (lambda () ((channel-get k-ch) p1)))
#<thread>
> (channel-get ch)
0

syntax

(parameterize* ((parameter-expr value-expr) ...)
  body ...+)

let*let에 대응하는 것처럼, parameterize*는 단일 파라미터 parameterize 폼들의 중첩된 연속과 같아요.

procedure

(make-derived-parameter parameter guard wrap [name realm]) → parameter?
  parameter : parameter?
  guard : (any/c . -> . any)
  wrap : (any/c . -> . any)
  name : symbol? = (object-name parameter)
  realm : symbol? = (procedure-realm parameter)

parameter와 같은 값을 설정하거나 조회하는 파라미터 프로시저를 돌려주지만, 다음과 같은 차이가 있어요:

  • guard: 파라미터를 설정할 때 적용돼요(parameter에 연관된 어떤 가드보다 먼저).

  • wrap: 파라미터 값을 얻을 때 적용돼요.

name 인자는 object-name이 보고하는 파라미터 프로시저의 이름으로, realmprocedure-realm이 보고하는 영역으로 사용돼요. 단지 parameter의 이름이나 영역을 교체하려는 것이라면 guardwrap 값을 제공해요.

파라미터 프로시저를 가드하는 데에도 쓸 수 있는 chaperone-procedure도 참고하세요.

base 패키지의 8.15.0.4 버전에서 namerealm 인자를 추가했어요.

procedure

(parameter? v) → boolean?
  v : any/c

v가 파라미터 프로시저면 #t를, 그렇지 않으면 #f를 돌려줘요.

procedure

(parameter-procedure=? a b) → boolean?
  a : parameter?
  b : parameter?

파라미터 프로시저 ab가 항상 같은 파라미터를 같은 가드로(다만 chaperone은 달라도 됨) 수정하면 #t를, 그렇지 않으면 #f를 돌려줘요.

procedure

(current-parameterization) → parameterization?

현재 연속의 파라미터화를 돌려줘요.

procedure

(call-with-parameterization parameterization thunk) → any
  parameterization : parameterization?
  thunk : (-> any)

parameterization을 현재 파라미터화로 하여(꼬리 호출로) thunk를 호출해요.

procedure

(parameterization? v) → boolean?
  v : any/c

vcurrent-parameterization이 돌려준 파라미터화면 #t를, 그렇지 않으면 #f를 돌려줘요.

더 알아보기

  • Parameters — 파라미터 모델의 기본 정보.
  • parameterize — Racket Guide의 동적 바인딩 설명.