Blog

Lisp1 vs Lisp2, Apply, Map, Reduce, Funcall

February 8, 2014

Lisp1 vs Lisp2, Apply, Map, Reduce, Funcall


Funtional Programming

함수형 프로그래밍에 대해서 고민했었던 몇 가지를 해결하는 시간.

  1. Lisp-1 vs Lisp-2
  2. Apply vs Funcall
  3. Apply vs Reduce
  4. Map vs Mapcar


1. Lisp-1 vs Lisp-2

Why should I use ‘apply’ in Clojure? 란 질문이 스택오버플로우에 있다. 작성자는 apply 를 언제 써야 하는가, 라는 질문을 올리면서 Lisp-1Lisp-2 와 무슨 상관이냐고 질문을 올렸다. 아래는 Rich Hickey 의 글 중에서 Lisp-1 Lisp-2의 차이점에 관한 부분을 인용

A big difference between Clojure and CL is that Clojure is a Lisp-1, so funcall is not needed, and apply is only used to apply a function to a runtime-defined collection of arguments. So, (apply f [i]) can be written (f i).

위키디피아에 의하면 Lisp-1Lisp-2 의 차이는 이렇다.

The namespace for function names is separate from the namespace for data variables. This is a key difference between Common Lisp and Scheme. For Common Lisp, operators that define names in the function namespace include defun, flet, labels, defmethod and defgeneric.

To pass a function by name as an argument to another function, one must use the function special operator, commonly abbreviated as #’. The first sort example above refers to the function named by the symbol > in the function namespace, with the code #’>.

Scheme’s evaluation model is simpler: there is only one namespace, and all positions in the form are evaluated (in any order) – not just the arguments. Code written in one dialect is therefore sometimes confusing to programmers more experienced in the other. For instance, many Common Lisp programmers like to use descriptive variable names such as list or string which could cause problems in Scheme as they would locally shadow function names.

Whether a separate namespace for functions is an advantage is a source of contention in the Lisp community. It is usually referred to as the Lisp-1 vs. Lisp-2 debate. Lisp-1 refers to Scheme’s model and Lisp-2 refers to Common Lisp’s model. These names were coined in a 1988 paper by Richard P. Gabriel and Kent Pitman, which extensively compares the two approaches

요약하자면, Lisp-1Scheme 과 같이 변수와 함수가 동일한 Namespace를 사용하는 Lisp 이고, Lisp-2Common Lisp 처럼 변수와 함수가 분리된 Namespace 를 사용하는 Lisp 이다. 다시 말해 Lisp-2 는 2가지의 네임스페이스를 사용한다는 이야기.

따라서 Common Lisp(CL) 같은 경우는 변수와 함수의 네임스페이스가 구분되어있으므로, apply 와 같은 고차함수를 사용할때 두번째 인자로 오는 함수가, 변수가 아니라 함수임을 알려주기 위해서(네임스페이스가 달라 중복될 수 있으므로) #' 를 통해 호출한다.

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

2. Apply vs Funcall in Lisp

그러면, 다시 질문으로 돌아가자. 언제 apply 를 써야 하는가? 여기 에 의하면, apply 는 인자로 리스트 가 오고, funcall나열된 인자들 이 온다.

(funcall function arg1 arg2 ...) 
==  (apply function arg1 arg2 ... nil) 
==  (apply function (list arg1 arg2 ...))

> (funcall #'+ 1 2)
3
> (apply #'+ 1 2 ())
3

(defun passargs (&rest args) (apply #'myfun args))
(defun passargs (a b) (funcall #'myfun a b))

3. Apply vs Reduce in Lisp

apply 는 두번째 인자인 function 에게 세번째 인자인 list 를 넘겨주는 것이고, reduce 는 두번째 인자로 들어오는 binary operation 인 function 을 이용해 3번째 인자인 list 를 처리한다.

Function apply
Applies the function to the args.

Function reduce
reduce uses a binary operation, function, to combine the elements of sequence

차이점은 아래 코드를 보면 극명하게 알 수 있다.

(reduce #'+ (list 1 2 3 4 5))
; translates to: (+ (+ (+ (+ 1 2) 3) 4) 5)

(apply #'+ (list 1 2 3 4 5))
; translates to: (+ 1 2 3 4 5)

// Some reduce examples
(reduce #'list '(1 2 3 4)) => (((1 2) 3) 4)
(reduce #'list '(1 2 3 4) :initial-value 0) => ((((0 1) 2) 3) 4)
(reduce #'list '(1 2 3 4) :initial-value 0 :from-end t) => (1 (2 (3 (4 0))))
(reduce #'list '(1 2 3 4) :from-end t) => (1 (2 (3 4)))
(reduce (lambda (x y) (+ (* x 10) y)) '(1 2 3 4)) => 1234
(reduce #'+ '(1 2 3 4)) => 10
(reduce #'* '(1 2 3 4) :initial-value 1) => 24

4. Map vs Mapcar in Lisp

map 이나 mapcar 모두 list 에 있는 각 인자들에 대해 function 을 적용하는 함수다. Javascript 에 있는 forEach 와 비슷하다. 차이점이 있다면 map 은 리턴 타입을 지정하고, mapcar 는 그렇지 않다는 점. 코드를 보면 쉽게 알 수 있다.

// map examples
(map 'list #'- '(1 2 3 4)) => (-1 -2 -3 -4) 
(map 'string 
     #'(lambda (x) (if (oddp x) #\1 #\0)) 
     '(1 2 3 4)) 
   => "1010"

// mapcar examples   
(mapcar #'car '((1 a) (2 b) (3 c))) =>  (1 2 3) 
(mapcar #'abs '(3 -4 2 -5 -6)) =>  (3 4 2 5 6)
(mapcar #'cons '(a b c) '(1 2 3)) =>  ((A . 1) (B . 2) (C . 3))

References

  1. Apply vs Call
  2. Why should I use ‘apply’ in Clojure?
  3. What is the difference between Lisp-1 and Lisp-2?
  4. Clojure : Apply vs Reduce