02 함수

2.1 데이터로서 함수

  • 리스프 자체가 함수 집합

    • 새로운 오퍼레이터를 추가할 수 있음.
  • 리스프 함수는

    • 런타임에 생성하고 반환 가능
    • 인수로 전달가능
    • 변수가 값으로 함수를 가질 수 있다.
    • 함수단위나 파일 단위로 컴파일할 수 있다.

2.2 함수 정의

  • 함수 정의 방법
    • defun
    • lambda

defun으로 함수 정의하기

(defun my-double (x)
  (* x 2))

#'my-double
;;=> #<Interpreted-Function C66ACE>

(my-double 3)
;;=> 6

lambda로 함수 정의하기

#'(lambda (x) (* x 2))
;;=> #<Interpreted-Function C674CE>

((lambda (x) (* x 2)) 3)
;;=> 6

함수/변수는 다른 이름공간을 가지고 있다.

이름 공간 분리ex
Lisp-1분리를 안함.Clojure
Lisp-2분리를 함.Common Lisp
(setq my-double 2)
;;=> 2

(my-double my-double)
;;=> 4

(symbol-value 'my-double)
;;=> 2

(symbol-function 'my-double)
;;=> #<Interpreted-Function C66ACE>
(setq x #'append)
;;=> #<Compiled-Function 46B4BE>

(eq (symbol-value 'x)
    (symbol-function 'append))
;;=> T

(eq #'my-double (car (list #'my-double)))
;;=> T

defun은 심볼을 함수 이름 공간에 추가한다.

(defun my-double (x)
  (* x 2))

(setf (symbol-function 'my-double)
      #'(lambda (x) (* x 2)))

2.3 인자로서의 함수

apply 사용법

(+ 1 2)
;;=> 3

(apply #'+ '(1 2))
;;=> 3

(apply (symbol-function '+) '(1 2))
;;=> 3

(apply #'(lambda (x y) (+ x y)) '(1 2))
;;=> 3

funcall 사용법

(apply #'+ 1 '(2))
;;=> 3

(funcall #'+ 1 2)
;;=> 3

mapcar 사용법

(mapcar #'(lambda (x) (+ x 10))
          '(1 2 3))
;;=> (11 12 13)

(mapcar #'+
        '(1 2 3)
        '(10 100 1000))
;;=> (11 102 1003)

sort 사용법

(sort '(1 4 2 5 6 7 3) #'<)
;;=> (1 2 3 4 5 6 7)

remove-if 사용법

(remove-if #'evenp '(1 2 3 4 5 6 7))
;;=> (1 3 5 7)

(defun our-remove-if (fn lst)
  (if (null lst)
      nil
      (if (funcall fn (car lst))
          (our-remove-if fn (cdr lst))
          (cons (car lst) (our-remove-if fn (cdr lst))))))

2.4 속성으로서의 함수

(defun behave (animal)
  (case animal
    (dog
     (wag-tail)
     (bark))
    (rat
     (scurry)
     (squeak))
    (cat
     (rub-legs)
     (scratch-carpet))
    (human
     (speak))))

(defun wag-tail () (print "wag-tail"))
(defun bark () (print "bark"))
(defun scurry () (print "scurry"))
(defun squeak () (print "squeak"))
(defun rub-legs () (print "rub-legs"))
(defun scratch-carpet () (print "scratch-carpet"))
(defun speak () (print "speak"))

(behave 'dog)
;;>> "wag-tail"
;;>> "bark"

(behave 'rat)
;;>> "scurry"
;;>> "squeak"

(behave 'cat)
;;>> "rub-legs"
;;>> "scratch-carpet"

(behave 'human)
;;>> speak
(defun behave2 (animal)
  (funcall (get animal 'behavior)))

(setf (get 'dog 'behavior)
      #'(lambda ()
          (wag-tail)
          (bark)))

(behave2 'dog)
;;>> "wag-tail"
;;>> "bark"

(setf (get 'all 'behavior)
      #'(lambda ()
          (bark)
          (scurry)
          (scratch-carpet)))

(behave2 'all)
;;>> "bark"
;;>> "scurry"
;;>> "scratch-carpet"

2.5 범위(Scope)

scope-test함수에서
bindingx매개변수 x와 바인딩되어있다.
free variabley스코프 환경에 따라 다르게됨
스코프y 의 값
다이나믹(dynamic)함수의 호출 체인을 거슬러 올라감5
렉시컬(lexical)함수가 정의 된 시점의 환경을 거슬러 올라감7
(let ((y 7))
  (defun scope-test (x)
    (list x y)))

;; 다이나믹 스코프
;; - y의 값: 함수 호출을 감싼 let으로 정의한 5.
(let ((y 5))
  (scope-test 3))
;;=> (3 5)

;; 렉시컬 스코프 - Common Lisp 기본 설정
;; - y의 값: 앞서 defun시 감싼 let으로 정의한 7.
(let ((y 5))
  (scope-test 3))
;;=> (3 7)

2.6 클로져(Closures)

함수에서 binding되지 않는 변수, 즉 free 변수가 있을 때, 그 변수를 포함하는 함수를 클로져라고 한다.

(defun list+ (lst n)
  (mapcar #’(lambda (x) (+ x n))
          lst))

(list+ ’(1 2 3) 10)
;;=> (11 12 13)
(let ((counter 0))
  (defun new-id ()
    (incf counter))
  (defun reset-id ()
    (setq counter 0)))

(defun make-adder (n)
  #’(lambda (x) (+ x n)))

(setq add2 (make-adder 2)
      add10 (make-adder 10))

(funcall add2 5)
;;=> 7
(funcall add10 3)
;;=> 13
(defun make-adderb (n)
  #’(lambda (x &optional change)
       (if change
           (setq n x)
           (+ x n))))

(setq addx (make-adderb 1))

(funcall addx 3)
;;=> 4

(funcall addx 100 t)
;;=> 100

(funcall addx 3)
;;=> 103
(defun make-dbms (db)
  (list
    #’(lambda (key)
        (cdr (assoc key db)))
    #’(lambda (key val)
        (push (cons key val) db)
        key)
    #’(lambda (key)
        (setf db (delete key db :key #’car))
        key)))

(setq cities (make-dbms ’((boston . us) (paris . france))))

(funcall (car cities) ’boston)
;;=> US

(funcall (second cities) ’london ’england)
;;=> LONDON

(funcall (car cities) ’london)
;;=> ENGLAND

(defun lookup (key db)
  (funcall (car db) key))

2.7 지역 함수

labels 사용법

(labels ((inc (x) (1+ x)))
  (inc 3))
;;=> 4

2.8 꼬리 재귀(Tail-Recursion)

our-length함수의 끝이 our-length으로 끝나는게 아니라 1+가 감싸는 형태로 되어있다. 이와 같은 형태는 꼬리 재귀 형태가 될 수 없다.

(defun our-length (lst)
  (if (null lst)
      0
      (1+ (our-length (cdr lst)))))

our-find-ifour-find-if로 끝나는 꼬리 재귀 형태이다.

(defun our-find-if (fn lst)
  (if (funcall fn (car lst))
      (car lst)
      (our-find-if fn (cdr lst))))

our-lengthlabels를 이용하여 내부에 꼬리 재귀 형태의 rec 함수를 정의하였다.

(defun our-length (lst)
  (labels ((rec (lst acc)
             (if (null lst)
                 acc
                 (rec (cdr lst) (1+ acc)))))
    (rec lst 0)))
;; Common Lisp에서 꼬리 재귀 최적화를 디폴트가 아닌 경우, 다음과 같이 추가 선언이 필요하다.
(proclaim '(optimize speed))
;; 속도 향상을 위한 꼬리 재귀 + 타입 선언 예제.
;; 1 ~ n 까지의 합을 구하는 함수.

(defun triangle (n)
  (labels ((tri (c n)
             (declare (type fixnum n c))
             (if (zerop n)
                 c
                 (tri (the fixnum (+ n c))
                      (the fixnum (- n 1))))))
    (tri 0 n)))

2.9 컴파일

  • compile함수 사용하여, 함수를 컴파일
  • compile-file함수 사용하여, 파일을 컴파일
(defun foo (x)
  (1+ x))
;;=> FOO


;; 컴파일 됐는지 여부 확인
(compiled-function-p #’foo)
;;=> NIL

(compile ’foo)
;;=> FOO

(compiled-function-p #’foo)
;;=> T

;; 익명함수 컴파일
(compile nil '(lambda (x) (+ x 2)))
;;=> #<Compiled-Function BF55BE>

;; 익명함수에 이름을 붙여 컴파일
(progn
  (compile 'bar '(lambda (x) (* x 3)))
  (compiled-function-p #’bar))
;;=> T


;; 렉시컬 환경에서는 컴파일이 안된다.
(let ((y 2))
  (defun foo (x)
    (+ x y)))

;; 함수를 리턴하는 함수를 컴파일하면, 리턴되는 함수도 컴파일이 되는걸 확인 할 수 있다.
(compile 'make-adder)
;;=> MAKE-ADDER
(compiled-function-p (make-adder 2))
;;=> T

;; 인라인 함수의 예제
(proclaim '(inline 50th))
(defun 50th (lst)
  (nth 49 lst))

(defun foo (lst)
  (+ (50th lst) 1) ;; => (+ (nth 49 lst) 1)
  )

2.10 Functions from Lists

패스

짚고 넘어가기

  • defun
  • eq
  • lambda
  • symbol-value
  • symbol-function
  • lisp1 / lisp2
  • setq
  • mapcar
  • funcall
  • labels
  • let
  • case
  • declare
    • type
  • proclaim
    • inline
  • optimize
  • compiled-function-p
  • compile
  • copmile-file
  • progn