FLET, LABELS, MACROLET 특수 연산자

FLET, LABELS, MACROLET 특수 연산자 (flet, labels, macrolet)

flet, labels, macrolet은 Common Lisp의 특수 연산자들로, 지역(local) 함수와 매크로를 정의하고 그 정의로 폼들을 실행해요. 전역 defun/defmacro와 달리 이 정의들은 주어진 몸체 안에서만 살아 있어요. flet은 재귀를 허용하지 않는 단순 지역 함수, labels는 자기 자신이나 서로를 참조하는 재귀·상호재귀 함수, macrolet은 지역 매크로를 만들 때 써요.

출처: CLHS: Special Operator FLET, LABELS, MACROLET

문법 (Syntax)

flet ((function-name lambda-list [[local-declaration* | local-documentation]] local-form*) *) 
     declaration* form* => result*
labels ((function-name lambda-list [[local-declaration* | local-documentation]] local-form*) *) 
       declaration* form* => result*
macrolet ((name lambda-list [[local-declaration* | local-documentation]] local-form*) *) 
          declaration* form* => result*

인자와 값 (Arguments and Values)

  • function-name — 함수 이름(function name)이에요.
  • name — 기호(symbol)예요.
  • lambda-list — 람다 리스트예요. fletlabels에서는 보통 람다 리스트(ordinary lambda list)이고, macrolet에서는 매크로 람다 리스트(macro lambda list)예요.
  • local-declarationdeclare 표현식이며, 평가되지 않아요.
  • declarationdeclare 표현식이며, 평가되지 않아요.
  • local-documentation — 문자열이며, 평가되지 않아요.
  • local-forms, forms — 암시적 progn이에요.
  • resultsform들의 값이에요.

설명 (Description)

flet, labels, macrolet은 지역 함수와 매크로를 정의하고, 그 지역 정의를 써서 form들을 실행해요. 폼들은 나타난 순서대로 실행돼요.

fletlabels가 만드는 각 함수와 macrolet이 만드는 각 매크로의 몸체 폼(람다 리스트는 제외)은, 해당 function-name이나 name의 함수 블록 이름(function block name)을 이름으로 하는 암시적 블록(implicit block) 안에 감싸져요.

fletlabels에서 지역 함수/매크로 정의 목록과 몸체 폼 사이에 있는 declaration들의 유효 범위는, 지역 정의된 함수들의 몸체는 포함하지 않아요. 다만 labels의 경우, 지역 정의된 함수를 가리키는 inline, notinline, ftype 선언은 지역 함수 몸체에 적용돼요. 다시 말해 그 선언들의 유효 범위는 그들이 영향 주는 함수 이름과 같아요. 이 declaration들의 유효 범위는 macrolet이 정의한 매크로 확장 함수들의 몸체는 포함하지 않아요.

flet

flet은 지역적으로 이름 붙은 함수를 정의하고, 이 정의 바인딩을 써서 일련의 폼들을 실행해요. 이런 지역 함수는 몇 개든 정의할 수 있어요.

이름 바인딩의 유효 범위는 몸체만 포함해요. flet 몸체 안에서, flet이 정의한 것과 일치하는 function-name 참조는 같은 이름의 전역 함수 정의가 아니라 지역 정의된 함수를 가리켜요. 또한 flet의 유효 범위 안에서는, flet이 정의한 function-name의 전역 setf 확장자 정의도 적용되지 않아요. 이는 (defsetf f ...)에는 적용되지만 (defmethod (setf f) ...)에는 적용되지 않는다는 뜻이에요.

flet이 정의한 함수들의 이름은 어휘 환경(lexical environment)에 있어요. 그래서 지역 정의는 flet의 몸체 안에서만 유지돼요. 함수 정의 바인딩은 몸체 안에서만 보이지, 정의 자체는 보이지 않아요. 함수 정의 안에서, 정의되는 것과 일치하는 지역 함수 이름은 flet 에서 정의된 함수나 매크로를 가리켜요. flet은 전역 함수 이름을 지역적으로 가릴(shadow) 수 있고, 새 정의가 전역 정의를 참조할 수도 있어요.

local-documentation이 있으면 (실제로 만들어진다면) 대응하는 지역 함수에 문서 문자열(docstring)로 붙어요.

labels

labelsflet과 같은데, 정의된 함수 이름의 유효 범위가 함수 정의 자체까지 포함한다는 점이 달라요. 즉 labels로는 재귀·상호재귀 함수를 정의할 수 있어요.

macrolet

macroletdefmacro와 같은 형식으로 지역 매크로 정의를 세워요.

macrolet 몸체 안에서는, macrolet이 정의한 name들의 전역 setf 확장자 정의가 적용되지 않아요. 대신 setf가 매크로 폼을 확장하고 그 결과 폼을 재귀적으로 처리해요.

macrolet이 정의한 매크로 확장 함수들은 macrolet 폼이 나타난 어휘 환경에서 정의돼요. macrolet 안의 선언들, macroletsymbol-macrolet 정의는 지역 매크로 정의에 영향을 주지만, 지역 매크로 정의가 그 어휘 환경에 보이는 지역 변수나 함수 바인딩을 참조하면 결과는 정의되어 있지 않아요.

local-documentation이 있으면 대응하는 지역 매크로 함수에 문서 문자열로 붙어요.

예제 (Examples)

 (defun foo (x flag)
   (macrolet ((fudge (z)
                ;The parameters x and flag are not accessible
                ; at this point; a reference to flag would be to
                ; the global variable of that name.
                `(if flag (* ,z ,z) ,z)))
     ;The parameters x and flag are accessible here.
     (+ x
        (fudge x)
        (fudge (+ x 1)))))
 == 
 (defun foo (x flag)
   (+ x
      (if flag (* x x) x)
      (if flag (* (+ x 1) (+ x 1)) (+ x 1))))

매크로 확장 후 이렇게 됩니다. xflag가 함수 foo의 매개변수를 올바르게 가리키는 건, 확장을 만들어낸 매크로 호출 지점에서 그 매개변수들이 보이기 때문이에요.

 (flet ((flet1 (n) (+ n n)))
   (flet ((flet1 (n) (+ 2 (flet1 n))))
     (flet1 2))) => 6
 (defun dummy-function () 'top-level) => DUMMY-FUNCTION 
 (funcall #'dummy-function) => TOP-LEVEL 
 (flet ((dummy-function () 'shadow)) 
   (funcall #'dummy-function)) => SHADOW 
 (eq (funcall #'dummy-function) (funcall 'dummy-function))
=> true 
 (flet ((dummy-function () 'shadow))
   (eq (funcall #'dummy-function)
       (funcall 'dummy-function)))
=> false 
 (defun recursive-times (k n)
   (labels ((temp (n) 
              (if (zerop n) 0 (+ k (temp (1- n))))))
     (temp n))) => RECURSIVE-TIMES
 (recursive-times 2 3) => 6
 (defmacro mlets (x &environment env) 
   (let ((form `(babbit ,x)))
     (macroexpand form env))) => MLETS
 (macrolet ((babbit (z) `(+ ,z ,z))) (mlets 5)) => 10
 (flet ((safesqrt (x) (sqrt (abs x))))
   ;; The safesqrt function is used in two places.
   (safesqrt (apply #'+ (map 'list #'safesqrt '(1 2 3 4 5 6)))))
=> 3.291173
 (defun integer-power (n k) 
   (declare (integer n)) 
   (declare (type (integer 0 *) k))
   (labels ((expt0 (x k a)
              (declare (integer x a) (type (integer 0 *) k))
              (cond ((zerop k) a)
                    ((evenp k) (expt1 (* x x) (floor k 2) a))
                    (t (expt0 (* x x) (floor k 2) (* x a)))))
            (expt1 (x k a)
              (declare (integer x a) (type (integer 0 *) k))
              (cond ((evenp k) (expt1 (* x x) (floor k 2) a))
                    (t (expt0 (* x x) (floor k 2) (* x a))))))
     (expt0 n k 1))) => INTEGER-POWER
 (defun example (y l)
   (flet ((attach (x)
            (setq l (append l (list x)))))
     (declare (inline attach))
     (dolist (x y)
       (unless (null (cdr x))
         (attach x)))
     l))
 (example '((a apple apricot) (b banana) (c cherry) (d) (e))
          '((1) (2) (3) (4 2) (5) (6 3 2)))
=> ((1) (2) (3) (4 2) (5) (6 3 2) (A APPLE APRICOT) (B BANANA) (C CHERRY))

labels로 정의한 integer-powerexpt0expt1이 서로를 호출하는 상호 재귀의 전형적 예시예요. flet으로는 이런 정의가 불가능하죠.

영향 (Affected By)

없음.

예외 상황 (Exceptional Situations)

없음.

함께 보기 (See Also)

  • declare, defmacro, defun, documentation, let
  • Section 3.1 (Evaluation)
  • Section 3.4.11 (Syntactic Interaction of Documentation Strings and Declarations)

참고 (Notes)

flet으로는 재귀 함수를 정의할 수 없어요. labels를 쓰면 상호 재귀 함수도 정의할 수 있죠.

macrolet 폼이 최상위 폼이면, 몸체 폼들도 최상위 폼으로 처리돼요. Section 3.2.3 (File Compilation)을 보세요.

더 알아보기 (Learn more)