2019년 1월 2일 수요일

[on lisp] 3.1 Functional Design 함수형 프로그래밍

3. Functional Programming
이전 장에서는 리스프와 리스프 프로그램 모두 하나의 원재료로 만들어졌음을 알려줬다. (함수)
여타 다른 건축 자재처럼, 이것들의 품질은 우리가 만드는 것들의 종류와 방식까지 모두 영향을 미친다.

3.1 Functional Design
객체의 특징은 그것이 뭐로 만들어졌는지에 영향을 받는다.
목조건물과 석조건물은 다르다. 목조건물로 할 수 없는 것을 석조건물은 할 수 있다. 그 반대도 마찬가지다.
멀리서 건물을 볼 때, 그것이 나무인지 돌인지는 확인할 수 없어도, 건물의 전체적인 모양을 보고 그것이 무엇으로 만들어졌는지 알 수 있다.

Lisp함수의 특성은 Lisp 프로그램의 구조에 그와 상응하는 영향을 미친다.

Functional programming means writing programs which work by returning values instead of by performing side-effects.
함수형 프로그래밍은 사이드이펙트를 만드는 대신 값을 리턴하는 방식으로 프로그램을 짜는 것이다.

사이드이펙트는 파괴적인 객체변환(rplaca함수를 사용: cons의 car를 치환)과, 변수를 할당하는 것이다.(setq)
사이드이펙트가 더 적고, 국지적일경우(범위가 작아질 경우), 프로그램의 읽기, 테스트, 디버그가 쉬워진다.
리스프 프로그램이 항상 이런 스타일로 작성된 것은 아니지만, 시간이 흐르면서 리스프와 함수형 프로그래밍은 분리될 수 없게되었다.

예를들어, 다른 언어와 다른 것을 보여줄 것이다.
리스트를 역순으로 만들고 싶다고 하자.

리스트를 역순으로 변.경.하는 함수를 짜는대신에, 우리는 리스트를 받고 똑같은 요소를 가진 리스트를 리턴하는 함수를 만들자. (물론 역순)
(defun bad-reverse (lst)
  (let* ((len (length lst))
  (ilimit (truncate (/ len 2))))
    (do ((i 0 (1+ i))
  (j (1- len) (1- j)))
 ((>= i ilimit))
      (rotatef (nth i lst) (nth j lst)))))
(setq lst '(a b c))
(bad-reverse lst) ; NIL
lst ; (C B A)

(bad-reverse '(1 2 3 4 5))  ;; ?? !!
이름부터 bad-reverse다. 이건 리스트를 새로 받아서 변.경.하는 함수를 짠 것이다.
리턴 값은 뭔가? NIL 내가 원하는 값과는 관련이 없다.
이건 리스프의 스타일이 아니다.
더 알아야 할 점은 이런 추함은 전염된다는 것이다. 이런 함수는 나중에 사용하는 사람에게도 해를 끼치ㅐㄴ다.
그리고 사용하는 사람도 함수적인 프로그래밍을 할 수 없게 만든다. 기능적 제한을 만들어주는 꼴이다.

새로 만들어보자.
(defun good-reverse (lst)
  (labels ((rev (lst acc)
  (if (null lst)
      acc
   (rev (cdr lst) (cons (car lst) acc)))))
   
 (rev lst nil)))
(setq lst '(a b c))
(good-reverse lst) ; (C B A)
lst ; (A B C)
이렇게 하면 아주 함수적으로 만든것이다.

이 책의 저자는 사람의 특징을 판단할 때 그의 머리스타일을 보고 판단할 수 있다고 생각했다 한다.
이것이 사실이던 아니던 리스프 프로그램에서는 사실이다.
Functional Program은 imperative Program과는 다른 형태를 가지고 있다.

함수형 프로그램의 구조는 전적으로 표현식 내에 인수들의 구성(방식)으로 표현된다.

인수들이 (인덴트로) 들쑥날쑦하기 때문에, 함수적인 코드는 들여쓰기에서 더 많은 움직임이 있다.
함수적코드는 페이지 위에 액체 같고, 명령형코드는 단단하고 나무토막 같다.
(뭐 함수형 코드가 더 유동적으로 보이고, 명령형 코드는 덜 유동적이라 자유롭지 않다는 말인듯? 중요하지 않은 내용이므로 고민말고 넘어감)

심지어 멀리서 봐도, bad-reverse와 good-reverse의 형태는 어떤 것이 더 나은 프로그램인지 암시한다.
(확실히 함수형 프로그램은 들여쓰기가 계속 생겨서 마구마구 움직이는 것 같지만 명령형 코드는 들여쓰기가 없고 목재마냥 직사각형을 보이고 있다.)
비록짧지만 good-reverser가 더 효율적이다. O(n) instead of O(n2).

커먼리습에는 가실 reverse 기능을 내장하고 있다.
(setq a '(1 2 3))

(print (reverse a))

(print a)
이 기능은 기존 내용을 변경하지 않고 새로 리턴한다. 그러므로 반약에 a를 변경하고 싶다면? 새로 값을 넣어야 한다.
(setq a (reverse a))
good-reverse와 bad-reverse의 차이에서 간과한 점은 bad-reverse는 "cons"를 하지 않는다는 것이다.
새로운 list structure를 만드는 것이 아니라. 기존 리스트에 실행된다. 이런건 아주 위험할 수 있다.
리스트는 다른 곳에서 사용될 수도 있다. 하지만 효율성을 고려할 때 필요한 상황이 있을 수 있다.
... 중략 ...
함수형 프로그래밍은 일반적으로 좋은 생각이다. 특히 Lisp에서, 왜냐하면 Lisp은 이것을 제공하기 위해 진화해왔기 때문이다.

댓글 없음:

댓글 쓰기