lisp - 오류 - 리스프 하스켈




Common Lisp for SICP를 사용할 수 있습니까 아니면 Scheme이 유일한 옵션입니까? (4)

또한 Common Lisp을 사용할 수 있다고해도? 제도가 더 좋습니까?


Clojure와 같은 실용적인 언어로 갈 것이고, Clojure는 JVM에서 돌아가고 모든 자바 라이브러리를 사용할 수있다. 이것은 큰 장점이다. 또한 Clojure는 현대 Lisp이며 멋진 개념을 포용합니다. 그것은 성장하고 좋은 지역 사회를 가지고 있습니다. Clojure를 시험해보고 싶다면 Clojurecademy 라는 Clojurecade 를 제안 할 입니다.이 Clojurecademy 는 새로 온 사람들을 위해 만든 대화식 Clojure 기반 코스 플랫폼입니다.


그들은 유사하지만 동일하지 않습니다.

나는 당신이 계획을 가지고 간다면 더 쉬울 것이라고 믿습니다.


이미 몇 가지 Common Lisp을 알고 있습니까? 나는 그것이 당신이 'Lisp'의 의미라고 가정합니다. 이 경우 Scheme 대신 사용하는 것이 좋습니다. 당신도 모르는 사람이고 당신이 학습 경험을 위해서만 SICP를 통해 일하고 있다면 아마도 당신은 Scheme을 사용하는 것이 더 나을 것입니다. 새로운 학습자를위한 더 나은 지원을 제공하며, Scheme에서 Common Lisp로 번역 할 필요가 없습니다.

차이점이 있습니다. 특히, SICP의 매우 기능적인 스타일은 커먼 리스프에서 더 어색합니다. 왜냐하면 함수를 전달할 때를 인용해야하고 funcall 을 사용하여 변수에 바인드 된 함수를 호출해야하기 때문입니다.

그러나 Common Lisp을 사용하려면 EIC Bendersky의 SICP 태그 아래 에있는 SICP 코드의 Common Lisp 번역을 사용해보십시오.


Common Lisp과 함께 SICP를 사용하는 것이 가능하고 재미있다.

많은 문제없이 SICP로 학습을 위해 Common Lisp을 사용할 수 있습니다. 이 책에서 사용 된 Scheme 하위 집합은별로 정교하지 않습니다. SICP는 매크로를 사용하지 않으며 계속 사용하지 않습니다. DELAY와 FORCE가 있으며, 몇 줄에 Common Lisp로 작성할 수 있습니다.

또한 (function foo)(funcall foo 1 2 3) 사용하는 초보자가 함수 프로그래밍 부분을 배울 때 코드가 명확 (funcall foo 1 2 3) 기 때문에 실제로 더 좋습니다 (IMHO!). 변수와 람다 함수가 호출 / 전달되는 곳을 볼 수 있습니다.

Common Lisp의 테일 호출 최적화

Common Lisp에 단점이있는 큰 영역은 꼬리 호출 최적화 (TCO)입니다. Common Lisp은 표준에서 TCO를 지원하지 않습니다. (나머지 Lisp과의 상호 작용이 불명확하기 때문에 모든 컴퓨터 아키텍처가 직접 지원합니다 (JVM이라고 생각하는 것은 아닙니다). 모든 컴파일러가이를 지원하지는 않지만 (일부 Lisp 머신) 디버깅 / 추적을 수행합니다 / stepping harder, ...).

그걸로 사는 세 가지 방법이 있습니다 :

  1. 스택이 날아 가지 않기를 바랍니다. 나쁜.
  2. TCO를 지원하는 Common Lisp 구현을 사용하십시오. 몇 가지가 있습니다. 아래를 참조하십시오.
  3. DOTIMES, DO, LOOP 등을 사용하여 기능 루프 (및 유사한 구조)를 루프 (및 유사한 구조)로 다시 작성하십시오.

개인적으로 나는 2 또는 3을 권하고 싶습니다.

Common Lisp에는 TCO 지원 (SBCL, LispWorks, Allegro CL, Clozure CL 등)을 갖춘 우수하고 사용하기 쉬운 컴파일러가 있으며 개발 환경으로는 내장 된 것 또는 GNU Emacs / SLIME을 사용합니다.

SICP와 함께 사용하려면 SBCL 을 권장합니다. 기본적으로 TCO를 지원하며 컴파일러는 많은 코딩 문제 (선언되지 않은 변수, 잘못된 인수 목록, 많은 유형의 오류 등)를 잡아냅니다. 이것은 학습하는 동안 많은 도움이됩니다. 일반적으로 Common Lisp 인터프리터는 일반적으로 TCO를 지원하지 않으므로 코드가 컴파일되었는지 확인하십시오.

때로는 하나 또는 두 개의 매크로를 작성하고 Scheme과 비슷한 코드를 만들기 위해 일부 Scheme 함수 이름을 제공하는 것이 도움이 될 수 있습니다. 예를 들어 Common Lisp에 DEFINE 매크로를 사용할 수 있습니다.

고급 사용자의 경우 SICP에서 대부분의 코드를 실행해야하는 이전 스키마 구현 (의사 스키마라고 함)이 있습니다.

내 추천 : 여분의 마일을 가고 Common Lisp을 사용하고 싶다면 해보십시오.

필요한 변경 사항을 더 쉽게 이해할 수 있도록 몇 가지 예를 추가했습니다. 즉, 꼬리 호출 최적화 를 지원하는 Common Lisp 컴파일러가 필요합니다.

SICP의 간단한 코드를 살펴 보겠습니다.

(define (factorial n)
  (fact-iter 1 1 n))

(define (fact-iter product counter max-count)
  (if (> counter max-count)
      product
      (fact-iter (* counter product)
                 (+ counter 1)
                 max-count)))

Common Lisp에서 DEFINE 매크로를 사용하여 직접 사용할 수 있습니다.

(defmacro define ((name &rest args) &body body)
  `(defun ,name ,args ,@body))

이제 SBCL, CCL, Allegro CL 또는 LispWorks를 사용해야합니다. 이 컴파일러는 기본적으로 TCO를 지원합니다.

SBCL을 사용합시다.

* (define (factorial n)
    (fact-iter 1 1 n))
; in: DEFINE (FACTORIAL N)
;     (FACT-ITER 1 1 N)
; 
; caught STYLE-WARNING:
;   undefined function: FACT-ITER
; 
; compilation unit finished
;   Undefined function:
;     FACT-ITER
;   caught 1 STYLE-WARNING condition

FACTORIAL
* (define (fact-iter product counter max-count)
    (if (> counter max-count)
        product
        (fact-iter (* counter product)
                   (+ counter 1)
                   max-count)))

FACT-ITER
* (factorial 1000)

40238726007709....

또 다른 예 : 상징적 인 차별화

SICP는 차별화를위한 계획 예를 가지고 있습니다 :

(define (deriv exp var)
  (cond ((number? exp) 0)
        ((variable? exp)
         (if (same-variable? exp var) 1 0))
        ((sum? exp)
         (make-sum (deriv (addend exp) var)
                   (deriv (augend exp) var)))
        ((product? exp)
         (make-sum
           (make-product (multiplier exp)
                         (deriv (multiplicand exp) var))
           (make-product (deriv (multiplier exp) var)
                         (multiplicand exp))))
        (else
         (error "unknown expression type -- DERIV" exp))))

Common Lisp에서이 코드를 실행하는 것은 쉽습니다.

  • 일부 함수의 이름이 다르다면 number? CL에서 numberp 입니다.
  • CL:CONDelse 대신 T 를 사용 else
  • CL:ERROR 은 CL 형식 문자열을 사용합니다.

일부 기능에 대한 스키마 이름을 정의합시다. Common Lisp code :

(loop for (scheme-symbol fn) in
      '((number?      numberp)
        (symbol?      symbolp)
        (pair?        consp)
        (eq?          eq)
        (display-line print))
      do (setf (symbol-function scheme-symbol)
               (symbol-function fn)))

위의 매크로 define :

(defmacro define ((name &rest args) &body body)
  `(defun ,name ,args ,@body))

Common Lisp 코드 :

(define (variable? x) (symbol? x))

(define (same-variable? v1 v2)
  (and (variable? v1) (variable? v2) (eq? v1 v2)))

(define (make-sum a1 a2) (list '+ a1 a2))

(define (make-product m1 m2) (list '* m1 m2))

(define (sum? x)
  (and (pair? x) (eq? (car x) '+)))

(define (addend s) (cadr s))

(define (augend s) (caddr s))

(define (product? x)
  (and (pair? x) (eq? (car x) '*)))

(define (multiplier p) (cadr p))

(define (multiplicand p) (caddr p))

(define (deriv exp var)
  (cond ((number? exp) 0)
        ((variable? exp)
         (if (same-variable? exp var) 1 0))
        ((sum? exp)
         (make-sum (deriv (addend exp) var)
                   (deriv (augend exp) var)))
        ((product? exp)
         (make-sum
           (make-product (multiplier exp)
                         (deriv (multiplicand exp) var))
           (make-product (deriv (multiplier exp) var)
                         (multiplicand exp))))
        (t
         (error "unknown expression type -- DERIV: ~a" exp))))

LispWorks에서 해보 죠.

CL-USER 19 > (deriv '(* (* x y) (+ x 3)) 'x)
(+ (* (* X Y) (+ 1 0)) (* (+ (* X 0) (* 1 Y)) (+ X 3)))

Common Lisp의 SICP에서 스트림 예제

SICP의 3.5 장에 있는 책 코드를 참조하십시오. 우리는 위에서 CL에 추가 사항을 사용합니다.

SICP는 delay , the-empty-streamcons-stream the-empty-stream 언급하지만이를 구현하지는 않습니다. 우리는 Common Lisp에서 구현을 제공한다.

(defmacro delay (expression)
  `(lambda () ,expression))

(defmacro cons-stream (a b)
  `(cons ,a (delay ,b)))

(define (force delayed-object)
  (funcall delayed-object))

(defparameter the-empty-stream (make-symbol "THE-EMPTY-STREAM"))

이제 책에서 이식 가능한 코드가 제공됩니다.

(define (stream-null? stream)
  (eq? stream the-empty-stream))

(define (stream-car stream) (car stream))

(define (stream-cdr stream) (force (cdr stream)))

(define (stream-enumerate-interval low high)
  (if (> low high)
      the-empty-stream
    (cons-stream
     low
     (stream-enumerate-interval (+ low 1) high))))

이제 Common Lisp는 stream-for-each 다릅니다.

  • 우리는 begin 대신에 cl:progn 을 사용할 필요가있다 cl:progn
  • 함수 매개 변수는 cl:funcall 을 사용하여 호출해야합니다 cl:funcall

다음은 버전입니다.

(defmacro begin (&body body) `(progn ,@body))

(define (stream-for-each proc s)
  (if (stream-null? s)
      'done
      (begin (funcall proc (stream-car s))
             (stream-for-each proc (stream-cdr s)))))

또한 cl:function 사용하여 함수를 전달해야 cl:function .

(define (display-stream s)
  (stream-for-each (function display-line) s))

그러나 다음 예제는 작동합니다.

CL-USER 20 > (stream-enumerate-interval 10 20)
(10 . #<Closure 1 subfunction of STREAM-ENUMERATE-INTERVAL 40600010FC>)

CL-USER 21 > (display-stream (stream-enumerate-interval 10 1000))

10 
11 
12 
...
997 
998 
999 
1000 
DONE