2019년 1월 17일 목요일

[on lisp] 5.6 Recursion on Subtrees

5.6 Recursion on Subtrees
리스프 프로그램에서 흔히 볼 수 있는 또 다른 재귀 패턴이 있다. 서브트리의 재귀
이 패턴은 중첩 리스트로 시작하여 car와 cdr을 모두 재귀적으로 사용하려는 경우에 나타남.

lisp 리스트는 다재다능한 구조이다. 리스트는 시퀀스, 세트, 매핑, 배열 및 트리를 나타낼 수 있다.
리스트를 트리로 해석하는 몇 가지 방법이 있다. 가장 보편적인 것은 왼쪽 가지가 car이고 오른쪽 가지가 cdr인 이진 트리로 간주하는 것이다.
;; Figure 5.7: Lists as trees.
(a . b) 
(a b c) 
(a b (c d))
만약 리스트가 아래와 같은 형태로 고려되면 쉽게 해석할 수 있다.
(a b c) = (a . (b . (c . nil))) 
(a b (c d)) = (a . (b . ((c . (d . nil)) . nil)))
어떠한 형태의 리스트도 2진트리로 해석될 수 있다. copy-list와 copy-tree와 같은 공통 리스프 함수의 쌍 간의 구별을 돕는다.
전자는 리스트를 시퀀스로 간주하고 복사한다. 만약 리스트에 서브리스트(sublists)를 보유한다면, 그것이 해당 리스트의 요소일 뿐이라도 복사되지 않는다.
(setq x '(a b) listx (list x 1))
((A B) 1)
(eq x (car (copy-list listx)))
T
;; copy-list구현을 다시 보자.
;; copy-list
(lrec #'(lambda (x f) (cons x (funcall f))))
;; Figure 5.5: Function to define flat list recursers
;; 이전에 본 소스임.
;; 특이하게 함수를 리턴함
(defun lrec (rec &optional base) 
  (labels ((self (lst) 
    (if (null lst) 
     (if (functionp base) 
      (funcall base) 
   base) 
  (funcall rec (car lst) 
               #'(lambda () (self (cdr lst))))))) 
 #'self))

;; 반대로 copy-tree는 리스트를 트리로 간주한다.
;; 서브리스트들 전부 복사된다.
(eq x (car (copy-tree listx)))
NIL
copy-tree구현체를 봐보자.
(defun our-copy-tree (tree)
  (if (atom tree)
      tree
      (cons (our-copy-tree (car tree))
            (if (cdr tree) (our-copy-tree (cdr tree))))))

[on lisp] 5.2 Orthogonality (직교성)

5.2 Orthogonality (직교성)

An orthogonal language is one in which you can express a lot by combining a small number of operators a lot of different ways.
직교 언어는 많은 다른 방식으로 소수의 연산자를 결합하여 많은 것을 표현할 수 있는 언어이다.

장난감 블록은 서로 직각이다. 플라스틱 모델 키트는 전혀 직각이 아니다.
complement의 가장 큰 장점을 언어를 보다 직관적으로 만든다.
complement 이전, Common Lisp는 remove-if, remove-if-not 혹은 subst-if, subst-if-not 같은 함수 쌍들이 있었다.
이제는 complement를 통해 그들 중 절반이 없어도 일이 가능하다.

setf 매크로 또한 Lisp의 직교성을 향상시킨다.
이전의 Lisp방언은 종종 데이터 읽기와 쓰기를 위한 함수 쌍을 가지고 있었다.
예를들어, property-lists에서는 속성을 설정하는 한 가지 기능과 그 속성에 대해 질의 하는 다른 기능이 있다.
Common Lisp에는 후자만 있다. 속성을 설정하려면 setf와 함께 사용하면 된다.
;; Figure 5.1: Returning destructive equivalents.
(setf (get 'ball 'color) 'red)

(defvar *!equivs* (make-hash-table))

(defun ! (fn)
  (or (gethash fn *!equivs*) fn))  ;; 해시맵에 들어간 함수를 가져온다.

(defun def! (fn fn!) 
  (setf (gethash fn *!equivs*) fn!))  ;; 파괴적인 해시맵을 가져와서 fn의 키값에 fn!를 넣는다.
우리는 Common Lisp를 더 작게 만들지는 못하겠지만, 거의 비슷한 것을 할 수 있다: use a smller subset of it(더 작은 부분 집합을 사용하라)
complement함수와 setf함수 같이 새로운 operator(연산자)를 정의하면 이 목표를 달성하는데 도움이 될까?
기능을 쌍으로 그룹화하는 방법은 적어도 한 가지 더 있다.(반대기능을 하는 걸로 그룹핑하는 것 외에 다른방식으로 그룹핑할 수도 있다.)
remove-if와 delete-if, reverse와 nreverse, append와 nconc와 같이 많은 기능이 파괴적인 버전(desctructive version)을 쌍으로 제공한다.
이렇게 파괴적인,비파괴적인 함수를 대응(쌍)으로 연산자를 정의함으로써, 파괴함수를 직접 참조하지 않아도 된다.

Figure 5.1은 파괴적인 대응(쌍) 개념을 지원하는 코드를 포함하고있다.
일단 *!equivs*는 글로벌 변수이며 (make-hash-table)로 파괴적인 함수에 매핑된다.
느낌표(!)는 파괴적인 동등을 반환한다.
def!는 그것들을 설정한다.
!(bang)연산자의 이름은 Scheme규약에 따온 것인데 !가 사이드이펙트를 생성한다는 것이다.
자 이제 우리가 정의를 해보자.
(def! #'remove-if #'delete-if)
;; 이제 아래처럼 할 필요없이
(delete-if #'oddp lst)
;; 아래처럼 사용한다.
(funcall (! #'remove-if) #'oddp lst)
;; 여기 Common Lisp의 어색함(awkwardness)는 
;; Scheme에서 더 잘 볼 수 있는 생각의 우아함을 숨긴다.
;; 아래가 스킴이 생각하는 무언가인듯. 꽤나 우아하다.
((! remove-if) oddp lst)

특이하게 조립을 한다.
더 큰 직교성 뿐만 아니라, !연산자는 몇 가지 다른 이점을 가지고 있다.
프로그램을 더 선명하게 만든다. 왜냐하면 우리는 즉시 (! #'foo)가 foo와 같지만(여기에는 호출방식도 같고) 파괴적인 것을 볼 수 있다.
또한, 파괴적인 연산자는 소스코드에서 뚜렷하고 인지할 수 있는 형태로 나타나는데,
이것은 우리가 버그를 찾을 때 특별한 주의를 기울여야 하기 때문에 좋다.

function과 destructive counterpart사이의 관계는 일반적으로 런타임 이전에 알려 지므로 정의하는 것이 가장 효율것이다,
'!' 연산자를 정의할 때 매크로로 정의하던가, 혹은 !를 위한 read macro를 제공한다.

2019년 1월 15일 화요일

[on lisp] 5.5 Recursion on Cdrs 재귀함수

5.5 Recursion on Cdrs
재귀 함수는 lisp에서 매우 중요하기 때문에 이것들 빌드하는 유틸리티가 있어야할 가치가 있다.
이 섹션과 다음 섹션에서는 가장 일반적인 두 가지 유형을 작성하는 함수에 대해 알아보자.
Common Lisp에서 이 함수들을 살짝 어색하게 사용된다.
일단 우리가 매크로의 주제로 들어가게 되면, 우리는 이 기계에 좀 더 우아한 면을 넣는 방법을 알게 될 것이다.
'recursers'를 만드는 매크로는 섹션 15.2, 15.3에서 논의된다.

프로그램에서 반복되는 패턴은 높은 수준의 추상화로 작성될 수 있다는 신호다.
반복되는 패턴은 높은 수준의 추상화로 작성되었을 수 있었다는 신호다.
Lisp에서 일반적으로 이와 같은 함수보다 일반적으로 보이는 패턴은 아래와 같다.
(defun our-length (lst)
    (if (null lst)
        0
        (1+ (our-length (cdr lst)))))
;; or this
(defun our-every (fn lst)
    (if (null lst)
        t
        (and funcall fn (car lst))
        (our-every fn (cdr lst))))
구조적으로 이 두 함수는 공통점이 많다.
둘 다 리스트에 cdr를 재귀로 반복 연산을 하고, 동일한 표현식을 각 스텝에 실행한다.
;; Figure 5.5: Function to define flat list recursers.
;; rec 요소별로 실행되어야 할 녀석
;; base 디폴트값.
(defun lrec (rec &optional base)  ;; 함수를 리턴한다.
    (labels ((self (lst)  ;; 재귀함수 이름 self
                   (if (null lst)  ;; lst가 없으면
                       (if (functionp base)  ;; 함수인지 확인하고 실행 후 리턴한다.
                           (funcall base)  ;; 함수실행
                           base)  ;; 그냥 리턴
                           (funcall rec (car lst)  ;; lst의 첫번째를 가져오고 rec를 실행
                                    #'(lambda () ;; 그다음 내용을 함수에 감싼다.
                                              (self (cdr lst)))))))
            #'self))                     
이 패턴은 경험 많은 프로그래머가 생각하기를 멈추지 않고 읽고 사용할 수 있게하는 리스프 프로그램에서 자주보임(?)
패턴을 새로운 추상화에 묶는 방법에 문자는 발생하지 않는다.
하지만 패턴은 모두 동일하다. 이러한 함수를 직접 작성하는 대신 우리를 위해 생성해줄 함수를 작성할 수 있어야 한다.
Figure 5.5는 함수빌더(function-builder)가 있다. lrec("list recurser")
이 빌더는 리스트에서 연속적으로 cdrs를 사용하는데 이렇게 반복되는 부분을 생성할 수 있어야 한다.

lec의 첫번째 인수는 현재 리스트의 car와 두번째 인수는 재귀로 계속 호출 될 수 있는 함수이다.
lrec를 사용하여 our-length를 다음처럼 표현할 수 있다.
(lrec #’(lambda (x f) (1+ (funcall f))) 0)
리스트의 길이를 찾는데 요소를 확인할 필요나 중간에 멈출 필요가 없으므로 x는 항상 무시되고 f는 항상 호출된다.
그러나 our-every함수를 표현할 수 잇는 두 가지를 모두 가능성을 모두 표현해야 한다.
예를 들어 oddp
(lrec #’(lambda (x f) (and (oddp x) (funcall f))) t)
lrec의 정의에서는 label를 사용하여 self라는 로컬 재귀 함수를 작성한다.
재귀적일 경우 함수 rec에 두개의 매개변수가 전달된다. 리스트의 현재 car와 재귀 호출을 구현하는 함수를 전달한다.

재귀케이스가 'our-every'와 같은 함수에서 첫번째 인자가 false를 반환하면 바로 그곳에서 멈추고 싶다.
즉, 재귀적일 경우에 전달 된 인수는 값이 아니라 값이어야 하는 함수여야 한다.(값을 원하는 경우)

Figure 4.5는 lrec로 정의된 기존의 Common Lisp함수를 보여준다.
lrec를 호출하면 주어진 함수를 가장 효율적으로 구현할 수 있는 것은 아니다.
사실 이 장에서 정의된 lrec와 다른 재발생(재귀) 생성기(recurser generators)는 꼬리 재귀랑은 다르다.
이러한 이유로 그들은 초기 버전의 프로그램이나 속도가 중요하지 않은 부분에서 사용하기에 가장 적합하다.
Figure 5.6: Functions expressed with lrec.
; copy-list
(lrec #’(lambda (x f) (cons x (funcall f))))
; remove-duplicates
(lrec #’(lambda (x f) (adjoin x (funcall f))))
; find-if, for some function fn
(lrec #’(lambda (x f) (if (fn x) x (funcall f))))
; some, for some function fn
(lrec #’(lambda (x f) (or (fn x) (funcall f))))

2019년 1월 11일 금요일

[on lisp] 5.4 Composing Functions 함수 조합(되게 유익)

5.4 Composing Functions
함수 f의 보수는 ∼f로 표기된다.

5.1절에서 closure가 ∼을 Lisp 함수로 정의할 수 있게 함을 보였다.
함수에 대한 또 다른 공통 연산은 연산자 ◦로 표시되는 '합성'입니다.
f와 g가 함수라면, f ◦g는 함수이고 f ◦g (x) = f (g (x))이다.
클로저는 또한 ◦을 Lisp 함수로 정의하는 것을 가능하게합니다
아래 Figure5.3은 여러 함수를 취해 그 혼합물을 리턴하는 compose함수를 정의한다.
;; Figure 5.3: An operator for functional composition.
(defun compose (&rest fns) ;; &rest는 복수의 함수를 매개변수를 받겠다.
    (if fns ;; 함수가 존재한다면
        (let ((fn1 (car (last fns))) ;; 리스트의 마지막 함수 가져오기(compose는 오른쪽부터 합쳐진다)
                   (fns (butlast fns))) ;; 마지막 함수를 제외한 함수 리스트
              #'(lambda (&rest args) ;; fn1 함수가 어떤 매개변수를 받을지 모르니 args로 다 받음
                        (reduce #'funcall fns ;; 하나하나 함수를 누산한다.
                                :from-end t ;; 아 뒤에서 부터 실행하라는 뜻
                                :initial-value (apply fn1 args)))) ;; 첫번째 함수를 초기값으로 실행 
             #'identity)) ;; 함수가 없으면 identity 리턴
              
;; 사용법
(compose #'list #'1+) ;; 아래 값과 같다.
#'(lambda (x) (list 1+ x))
compose에 대한 인수로 주어진 모든 함수는 마지막 인수를 제외하고, 모두 하나의 인수를 받는 녀석들이어야 한다.
마지막 함수는 아무런 제약이 없다. 무엇이든지 인수가 주어지면 compose에 의해 함수가 초깃값으로 반환될 것이다.
> (funcall (compose #’1+ #’find-if) #’oddp ’(2 3 4))
4
위에 내용은 함수들을 closure로 감싼 함수를 리턴한 것과 같다.
;; Figure 5.4: More function builders.
(defun fif (if then &optional else)
    #'(lambda (x)
              (if (funcall if x)
                  (funcall then x)
                  (if else (funcall else x)))))
(defun fint (fn &rest fns)
    (if (null fns)
        fn
        (let ((chain (apply #'fint fns)))
             #'(lambda (x)
                       (and (funcall fn x) (funcall chain x))))))

(defun fun (fn &rest fns)
    (if (null fns)
        fn
        (let ((chain (apply #'fun fns)))
             #'(lambda (x)
                       (or (funcall fn x) (funcall chain x))))))

not은 리스프 함수이기 때문에, complement는 compose의 특별한 경우이다.
이녀석은 이렇게 정의될 수 있다.
(defun complement (pred)
  (compose #'not pred))
함수들을 조합(composing)하는 것 이외의 다른 방법으로 기능을 결합(combine)할 수 있다.
(mapcar #'(lambda (x)
                  (if (slave x) ; 노예면
                      (owner x) ; 오너를
                      (employer)) ; 아니면 고용주를
                      people) ; 사람 , 노예 아니면 직장인
위와 같은 함수를 자동으로 생성하는 연산자를 정의할 수 있다.
Figure 5.4의 fif를 사용하면 다음과 같은 효과를 얻을 수 있다.
(mapcar (fif #'slave #'owner #'employer)
        people)
;; 코드리뷰
(defun fif (if then &optional else)
    #'(lambda (x) ; if, then, else를 담은(closure) 람다함수 리턴
              (if (funcall if x) ; if 함수가 참이면
                  (funcall then x) ; then 함수 실행
                  (if else (funcall else x))))) ; else 함수 실행
Figure 5.4는 일반적으로 발생하는 유형의 함수에 대한 몇 가지 다른 생성자를 포함합니다.
두 번째, fint는 다음과 같은 경우입니다.
트루이면 그 상태에서 멈추는 거니까 거기까지만 일을 했다는 것! 특이한 건 그때그때 함수를 실행할 것이겠지?
and이니까 그럴 것이다.(and는 매크로로 보임)
(find-if #'(lambda (x)
                   (and (signed x) (sealed x) (delivered x)))
         docs)
find-if의 인수로 주어진 predicate(술어)는 그 안에서 호출되는 세 개의 predicates의 교차점(intersection)이다.
"function intersection"을 뜻하는 fint를 사용하여 다음과 같이 쓸 수 있다.
(find-if (fint #'signed #'sealed #'delivered) docs)
이렇게 유사한 연산자를 정의하여 predicate집합의 합집합을 반환 할 수 있따.
fun 함수는 fint와 비슷하지만 and 대신에 or를 사용한다.


2019년 1월 10일 목요일

[on lisp] 5.3 Memoizing 캐싱,메모

5.3 Memoizing
어떤 함수가 계산하는데 비용이 많이 들고, 때로는 같은 호출을 두 번 이상하는 경우가 있다.
이전의 모든 호출의 반환 값을 캐싱하고 함수가 호출 될 때마다 다음과 같이 memoize하는 것이 좋다.
값이 저장되었는지 확인하려면 먼저 캐시를 살펴보십시오.


그림 5.2는 일반화 된 memoizing 유틸리티입니다.
memoize 함수를 제공하고, 이전 호출 결과를 저장하는 해시테이블을 포함하는(closure)를 반환한다.
;; Figure 5.2: Memoizing utility.
(defun memoize (fn)
  (let ((cache (make-hash-table :test #'equal)))
    #'(lambda (&rest args)
     (multiple-value-bind (val win) (gethash args cache)  ;; 구조분해 같은 것인듯
    (if win  ;; 값이 있으면
     val  ;; 해당 값 리턴
     (setf (gethash args cache)  ;; 값이 없는 경우 setf로 해시에 가져온 키값에 설정한 값을 넣음
        (apply fn args))))))) ;; setf는 넣은 값 리턴으로 보임.
     
     
> (setq slowid (memoize #'(lambda (x) (sleep 5) x)))
Interpreted-Function C38346
> (time (funcall slowid 1))
Elapsed Time = 5.15 seconds
1
> (time (funcall slowid 1))
Elapsed Time = 0.00 seconds
1    
memoized 함수를 사용하면, 반복 호출은 해시 테이블 조회 일뿐이다.
물론 초기 호출마다 조회 비용이 추가되지만, 계산하기에 충분히 비싼 함수를 메모하는 것이므로 비용은 비교할 때 중요하지 않다고 가정하는 것이 합리적이다.
대부분의 용도에 적합하지만, memoize의 구현에는 몇 가지 제한이 있다.
이 함수는 동일한 인수 목록이 있으면 동일하게 리턴값이 나와야 한다.
만약 함수에 키워드 파라미터가 있으면 너무 엄격할 수 있다.
또한 단일 값 함수에만 사용되며 여러 값을 저장하거나 반환 할 수 없다.

[on lisp] 5. Returning Functions 함수 리턴(클로저)

5. Returning Functions
이전 장에서는 함수를 인수로 전달하는 기능이 추상화 가능성을 어떻게 증가시키는지 보았다.
우리는 함수로 더 많은 것을 할수록, 우리는 더 많은 가능성을 취할 수 있다.
새로운 함수를 구축하고 새로운 함수를 리턴함으로써, 그리는 함수를 인수로 이용하는 유틸리티의 효과를 확대할 수 있다.

이 장에서 유틸리티는 함수를 실행한다.
커먼리습에서 매크로처럼 표현식에 적용하기 위해 많은 것을 작성하는 것은 자연스러운 것이다.
매크로 계층은 15장의 일부 연산자와 중첩된다. 하지만 우리가 매크로를 통해서만 이러한 함수를 호출할지라도, 함수로 수행할 수 있는 작업의 부부능ㄹ 아는 것은 중요하다.

5.1 Common Lisp Evolves
커먼리습은 본래 몇 쌍의 보완 함수(Complementary functions)를 제공한다.
remove-if, remove-if-not 함수가 이런 한 쌍이다. 만약 pred가 하나의 인수인 predicate(술어)라면
(remove-if-not #'pred lst)
;; 이건 아래와 같다.
(remove-if #'(lambda (x) (not (pred x))) lst)
하나의 인자로 주어진 함수를 다양화함으로써, 우리는 다른 하나의 효과를 복제할 수 있다.
CLTL2에서는 다음과 같은 경우를 위한 새로운 함수를 포함한다.
complement함수는 predicate(술어)p를 취하여 항상 반대값을 반환하는 함수를 반환한다.
p가 true를 반환하면, complement(보수)는 false를 반환하고 그 반대도 마찬가지이다.
(remove-if-not #'pred lst)
;; 아래와 같다.
(remove-if (complement #'pred) lst)
complement함수오 함께라면 if-not함수를 계쏙 사용할 이유가 없다.
실제로 CLTL(p.391)에서 deprecated 되었다고 말한다. 그들이 커먼리습에 남아있다면 오로지 호환성을 위해서일 것이다.
새로운 complement 연산자는 중요한 빙산의 일각이다. : (함수를 반환하는 함수: functions which return functions)

이것은 오랫동안 Scheme의 관용구에서 중요한 부분이었다.
Scheme은 함수를 lexcial closure로 만드는 첫번째 리스프였고, 리턴 값으로 함수를 가지는 것을 흥미롭게 보았다.
dynamically scoped Lisp가 함수를 반환 할 수 없는 것은 아니다.
아래의 함수는 dynamic이나 lexcial scope 둘다 동일하게 작동한다.
(defun joiner (obj)
  (typecase obj
    (cons #'append)
    (number #'+)))
객체를 취하고, 이것의 타입에 따라 객체를 더하는 함수를 반환한다.
우리는 숫자들이나, 리스트들에 작동하는 join 함수를 다형성을 적용시켜 작동하게 할 수 있을 것이다.
(defun join (&rest args)
  (apply (joiner (car args)) args))
그러나 상수 함수(constant functions)를 반환하는 것은 동적스코프가 수행 할 수 있는 작업의 한계다.
동적스코프가 할 수 없는 것은 런타임에 함수를 빌드하는 것이다.
joiner는 두 가지 함수 중 하나를 반환 할 수 있지만 두 가지 선택으로 고정되어 있다.
이전에 우리는 렉시컬 스코프를 사용하여 함수를 리턴하는 함수를 본 적이 있다.
(defun make-adder (n)
  #'(lambda (x) (+ x n)))
make-adder를 호출하면 인수로 주어진 값에 따라 동작(바뀌는!) 클로저(closure)가 생성된다.
> (setq add3 (make-adder 3))
#Interpreted-Function BF1356
function to add such objects together. We could use it to define a polymorphic join function that worked for numbers or lists: 
(defun join (&rest args)
  (apply (joiner (car args)) args))
However, returning constant functions is the limit of what we can do with dynamic scope. What we can't do (well) is build functions at runtime; joiner can return one of two functions, but the two choices are fixed. 
On page 18 we saw another function for returning functions, which relied on lexical scope: 
(defun make-adder (n)
  #'(lambda (x) (+ x n)))
Calling make-adder will yield a closure whose behavior depends on the value originally given as an argument: 
> (setq add3 (make-adder 3))
#Interpreted-Function BF1356
> (funcall add3 2)
5
렉시컬 소코프(Lexical scope)에서, 단순히 상수 함수 그룹을 선택하는 대신!, 런타임에서 새로운 클로저를 생성할 수 있다.
동적 스코프(Dynamic scope)를 사용하면 이런건 불가능하다.
complement가 어떻게 작성될 것인지를 생각해보면, 이것 역시 closure를 리턴해야 한다는 것을 알 수 있다.
(defun complement (fn)
  #'(lambda (&rest args) (not (apply fn args))))
;; not을 안에 붙여서 함수를 생성 하도록하는데 fn을 머금는 클로저로 함수를 뱉는다. 
;; (이것은 렉시컬 스코프라 가능한 것)
complement에 의해 반환되는 함수는 complement가 호출될 때 변수 fn의 값을 사용한다.
그러므로, 상수 함수 그룹을 선택하는 대신 'complement'는 모든 함수의 역함수를 사용자-정의(custom-build) 할 수 있다.
> (remove-if (complement #'oddp) '(1 2 3 4 5 6))
(1 3 5)

함수를 인수로 전달할 수 있다는 것은 추상화를 위한 강력한 도구입니다.
함수를 반환하는 함수를 작성하는 기능으로 우리는 함수를 최대한 활용할 수 있다.
나머지 절에서는 기능을 리턴하는 유틸리티의 몇 가지 예를 제시한다.

2019년 1월 9일 수요일

[on lisp] 4.8 Density

4.8 Density

만약 당신이 코드가 많은 새로운 유틸리티를 사용한다면, 일부 독자들은 그것이 이해하기 어렵다고 불평할지도 모른다.

아직 Lisp에 능숙하지 않은 사람들은 raw Lisp를 읽는 데만 익숙해질 것이다.
사실 그들은 아직 확장 가능한 언어에 전혀 익숙하지 않을 수도 있다.
그들이 'utility'에 크게 의존하는 프로그램을 볼 때마다
이 언어의 순수한 괴팍함에 작성자가 사적인 언어로 이 프로그램을 쓰기로 결정한 것처럼 보인다.

새로운 연산자(operators)는, 논란이 될 수도 있지만, 프로그램을 읽기 어렵게 만든다.
프로그램을 읽기 전에 그것들을 모두 이해해야 한다.
왜 이런 주장이 잘못되었는지 알기 위해서, 페이지 41을 고려해라.(?? 유틸리티 함수를 만드는 섹션 가라는 말인듯, 왜냐하면 find2가 있음)
만약 당신이 find2를 사용하여 프로그램을 작성했다면, 누군가가 당신의 프로그램을 읽기 전에 이 새로운 유틸리티의 정의를 이해해야 한다고 불평할 수 있다.
글쎄, 만약 당신이 find2를 사용하지 않았다고 가정해보자.
그렇다면, find2의 정의를 이해해야 하는 대신에, 이해해야 할 것이 있을 것이다.
그러면 코드를 읽는 사람은 find2의 기능과 함께 다른 특정한 기능이 있는 함수를 한꺼번에 이해해야 한다.
(서점에서 책을 찾는다 해보면, 서점 도메인에 정의되어 있는 내용과 find2를 한 곳에서 이해해야 한다)
그리고 정확한건 이렇게 섞이면 분리된 것보다 이해하기 어렵다.

그리고 알아야 할 점이 있다.
우리는 지금 예제를 만든 것이기 때문에 새로 만든 유틸리티를 한 번밖에 사용하지 않았따. 유틸리티는 반복적으로 사용하기 위해 만들어진다.
실제 프로그램에서 find2 같은 것과 3~4가지 특수(특정 도메인에만 작동)한 검색 루틴을 이해해야 할 필요가 있을 수 있다.
확실히 전자가 더 쉽다.

그렇다, 상향식-프로그램을 읽으려면 저자에 의해 정의된 모든 새로운 연산자(operator)를 이해해야 한다.
그러나 이거 없이 요구를 충족하며 만들어진 모든 코드를 이해해야 하는 것보다 더 적은 작업일 것이다.

만약 사람들이 '유틸리티'를 사용하면 코드를 읽기가 어렵게 만든다고 불평한다면, 코드를 사용하지 않았다면 어떻게 보일지 알지 못한 것이다.
상향식-프로그래밍은 큰 프로그램인 거와 달리, 작고 심플하게 보이게 한다.
이것은 프로그램이 별로 일을 하지 않는다는 인상을 주고 그것은 읽기 쉽게 보일 수 있다.
경험없는 독자가 더 가까이에서 보았을 때, 이것이 그렇지 않다는 것을 알게되면, 그들은 충격을 받는다.

우리는 다른 분야에서도 동일한 현상을 발견한다. 잘 설계된 기계는 부품을 더적게 차지할 수 있지만, 더 작은 공간에 포장되기 때문에 더 복잡해 보인다.
상향식 프로그램은 개념적으로 밀도가 높다. 그것들을 읽으려면 노력이 필요할지도 모르지만, 상향식으로 짜여지지 않은 것들 만큼은 아니다.

유틸리티 사용을 의도적으로 피할 수 있는 경우가 있다: 작은 프로그램을 짜서 독립적으로 나머지 코드에 배포되야할 녀석입니다.
유틸리티는 일반적으로 2~3회 사용후 자체적으로 비용을 지불하지만(2,3회 쓰이면 괜찮음 만들라) 작은 프로그램에서 유틸리티를 만들어 작성해봤자
그걸 여러번 얼마나 쓸지 장담할 수 없으니 정당화 될 수 없다.