2019년 1월 2일 수요일

[on lisp] 2.6 Closures 클로저 사용하기

2.6 Closures

커먼리습은 lexical scope이기 때문에, 함수를 free variable을 포함하여 정의할 수 있다.
free variable을 포함하여 정의를 하면 시스템은 함수가 정의된 시점에 해당 변수에 바인딩된 녀석을 찾고 사본을 저장해야 한다.

이런 함수와 변수바인딩의 조합을 closure(클로저)라고 한다.
클로저는 커먼리습에서 너무 만연하여 자신도 모른체 사용하고 있을 수도 있다.


Every time you give mapcar a sharp-quoted lambda-expression containing free variables, you're using closures.
mapcar에 #'(lambda ...)에 free variable이 포함된 것을 봣다면 지금까지 closure를 쓴것이다.

예를들어, 리스트를 받아서 특성 숫자를 각 요소에 더하는 함수를 만든다고 하자.
(defun list+ (lst n)
  (mapcar #'(lambda (x) (+ x n))
          lst))
(list+ '(1 2 3) 10)
list+를 자세히 보면 closure가 적용된 것을 알 수 있다.
잘 보면 lambda 함수 안에 n은 free variable이다. 이 녀석은 둘러쌓인 환경(defun list+ ...)에서 바인딩 된다.
이렇게 함수를 매핑하는 곳에는 왠만하면 closure가 있다.

Closure는 렉시컬 스코프에서 쓰이는 로컬변수의 상태라고 생각해보자.(블로거 생각)
이런 로컬 변수를 사용하는 경우를 보자.
(let ((counter 0))
  (defun new-id () (incf counter))  ;; incf 는 counter를 1 증가시킨다. 
  (defun reset-id () (setq counter 0)))
위의 두 함수를 counter 변수를 공유한다.
첫번째는 counter를 +1 하고 두번째 함수를 counter를 0으로 변경한다.
이렇게 두 함수가 같은 변수로 연결될 수 있다.
이처럼 local state와 함께 함수를 리턴하는 것은 아주 유용한 기술이다.
(defun make-adder (n)
  #'(lambda (x) (+ x n)))
이렇게 하면 free-variable n은 make-adder의 매개변수에 바인딩 되고 그 카피본과 함께 함수를 리턴된다.
리턴된 함수를 n을 가지고 있게 되는 것이다.
(setq add2 (make-adder 2)
function :LAMBDA (X) (+ X N)
(funcall add2 5)
7
위에서 make-adder에서 리턴된 내부 상태는 변경되지 않는다. 하지만 우리는 가능하게 할 수 있다.
(defun make-adderb (n)
  #'(lambda (x &optional change)
      (if change
       (setq n x)
    (+ x n))))
(setq addx (make-adderb 1))
(funcall addx 3) ;; 4
;; change optional
(funcall addx 100 t)
(funcall addx 3) ;; 103

클로저 덩어리를 리턴하는 것도 가능하다.
당연히 함수를 일반 객체처럼 쓸 수 있으니, 리스트에 넣어서 빼쓰는 거라든게 해시맵에 저장하는 거라든가 모든게 가능할 것이다.

댓글 없음:

댓글 쓰기