[lisp] Common Lisp의 LET 대 LET *

LET와 LET * (병렬 바인딩 대 순차 바인딩)의 차이점을 이해하고 있으며 이론적으로는 완벽하게 이해됩니다. 하지만 실제로 LET가 필요한 경우가 있습니까? 최근에 살펴본 모든 Lisp 코드에서 모든 LET를 변경없이 LET *로 바꿀 수 있습니다.

편집 : 좋아, 어떤 사람이 LET *를 매크로로 만들 었는지 이해 합니다. 내 질문은 LET *가 존재한다는 점을 감안할 때 LET가 주변에 머물러야하는 이유가 있습니까? LET *가 평범한 LET만큼 잘 작동하지 않는 실제 Lisp 코드를 작성 했습니까?

나는 효율성 논쟁을 사지 않는다. 첫째, LET *가 LET만큼 효율적인 것으로 컴파일 될 수있는 경우를 인식하는 것은 그렇게 어렵지 않은 것 같습니다. 둘째, CL 사양에는 효율성을 중심으로 설계된 것처럼 보이지 않는 많은 것들이 있습니다. (타입 선언이있는 LOOP를 마지막으로 본 게 언제입니까? 그것들이 사용되는 것을 본 적이 없다는 것을 알아 내기가 너무 어렵습니다.) 1980 년대 후반의 Dick Gabriel의 벤치 마크 이전에 CL 완전히 느 렸습니다.

이것은 이전 버전과의 호환성의 또 다른 경우 인 것 같습니다. 현명하게도 LET만큼 근본적인 것을 깨뜨릴 위험을 감수하고 싶지 않은 사람은 아무도 없습니다. 내 직감 이었지만, LET가 LET *보다 엄청나게 쉽게 많은 것들을 만들었던 내가 놓친 어리 석고 단순한 케이스가 아무도 없다는 소식을 들으면 위로가됩니다.



답변

LET으로 대체 될 수 있기 때문에 그 자체는 함수형 프로그래밍 언어 에서 실제 원시가 아닙니다 LAMBDA. 이렇게 :

(let ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

~와 비슷하다

((lambda (a1 a2 ... an)
   (some-code a1 a2 ... an))
 b1 b2 ... bn)

그러나

(let* ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

~와 비슷하다

((lambda (a1)
    ((lambda (a2)
       ...
       ((lambda (an)
          (some-code a1 a2 ... an))
        bn))
      b2))
   b1)

어느 것이 더 간단한 지 상상할 수 있습니다. LET및 아닙니다 LET*.

LET코드를 더 쉽게 이해할 수 있습니다. 하나는 여러 개의 바인딩을보고 하나는 ‘효과'(리 바인딩)의 하향식 / 왼쪽-오른쪽 흐름을 이해할 필요없이 각 바인딩을 개별적으로 읽을 수 있습니다. 사용 LET*프로그래머 바인딩 독립 아니라는 것을 (코드를 읽는 일)에 신호를하지만, 다운 상단의 어떤 흐름이있다 – 일을 복잡하게한다.

Common Lisp에는 바인딩 값 LET이 왼쪽에서 오른쪽으로 계산 된다는 규칙이 있습니다. 함수 호출의 값이 평가되는 방식-왼쪽에서 오른쪽으로. 따라서 LET개념적으로 더 간단한 문장이며 기본적으로 사용해야합니다.

유형LOOP ? 꽤 자주 사용됩니다. 기억하기 쉬운 몇 가지 기본 형식 선언 형식이 있습니다. 예:

(LOOP FOR i FIXNUM BELOW (TRUNCATE n 2) do (something i))

위의 변수 ifixnum.

Richard P. Gabriel은 1985 년에 Lisp 벤치 마크에 대한 그의 책을 출판했으며 당시 이러한 벤치 마크는 비 CL Lisps에서도 사용되었습니다. Common Lisp 자체는 1985 년에 완전히 새로워졌습니다 . 언어를 설명 하는 CLtL1 책은 1984 년에 막 출판되었습니다. 그 당시 구현이 그다지 최적화되지 않은 것은 당연합니다. 구현 된 최적화는 기본적으로 이전 구현 (예 : MacLisp)과 동일 (또는 그 이하)이었습니다.

그러나 대한 LETLET*주요 차이점 사용하여 해당 코드는 LET이 측면으로 올바른 평가를 왼쪽 (설정하지 않는 변수를 활용하는 나쁜 스타일이 특히 때문에 – 바인딩 조항은 서로 독립적이기 때문에, 인간에 대한 이해하기 쉽게 효과).


답변

LET 는 필요 하지 않지만 일반적으로 원합니다 .

LET는 까다로운 일이없이 표준 병렬 바인딩을 수행하고 있음을 제안합니다. LET *는 컴파일러에 대한 제한을 유도하고 사용자에게 순차 바인딩이 필요한 이유를 제안합니다. 스타일 측면에서 LET *에 의해 부과 된 추가 제한이 필요하지 않을 때 LET가 더 좋습니다.

LET *보다 LET를 사용하는 것이 더 효율적일 수 있습니다 (컴파일러, 최적화 프로그램 등에 따라 다름).

  • 병렬 바인딩은 병렬로 실행할 수 있습니다 (그러나 LISP 시스템이 실제로이를 수행하는지 여부는 알 수 없으며 init 양식은 여전히 ​​순차적으로 실행되어야합니다)
  • 병렬 바인딩은 모든 바인딩에 대해 하나의 새 환경 (범위)을 만듭니다. 순차 바인딩은 모든 단일 바인딩에 대해 새로운 중첩 환경을 만듭니다. 병렬 바인딩은 메모리를 적게 사용 하고 변수 조회더 빠릅니다 .

(위의 글 머리 기호는 Scheme, 다른 LISP 방언에 적용됩니다. clisp는 다를 수 있습니다.)


답변

나는 인위적인 예를 들었다. 이 결과를 비교하십시오.

(print (let ((c 1))
         (let ((c 2)
               (a (+ c 1)))
           a)))

이것을 실행 한 결과 :

(print (let ((c 1))
         (let* ((c 2)
                (a (+ c 1)))
           a)))


답변

LISP에서는 가능한 가장 약한 구조를 사용하려는 욕구가 종종 있습니다. 예를 들어 일부 스타일 가이드는 비교 된 항목이 숫자라는 것을 알 =때보 다 사용하라고 알려줍니다 eql. 아이디어는 종종 컴퓨터를 효율적으로 프로그래밍하기보다는 의미하는 바를 지정하는 것입니다.

그러나 더 강력한 구조를 사용하지 않고 의미하는 바만 말하면 실제 효율성이 향상 될 수 있습니다. 를 사용한 초기화가있는 경우 LET병렬로 실행할 수 있지만 LET*초기화는 순차적으로 실행해야합니다. 어떤 구현이 실제로 그렇게할지 모르겠지만 일부는 미래에 잘 될 수 있습니다.


답변

저는 최근에 두 인수의 함수를 작성했는데, 어떤 인수가 더 큰지 알면 알고리즘이 가장 명확하게 표현됩니다.

(defun foo (a b)
  (let ((a (max a b))
        (b (min a b)))
    ; here we know b is not larger
    ...)
  ; we can use the original identities of a and b here
  ; (perhaps to determine the order of the results)
  ...)

치죠는 b우리가 사용했던 경우, 크다 let*, 우리가 실수로 설정 한 것 ab같은 값에.


답변

LET와 LET *의 공통 목록의 주요 차이점은 LET의 기호는 병렬로 바인딩되고 LET *의 기호는 순차적으로 바인딩된다는 것입니다. LET를 사용하면 init-form을 병렬로 실행할 수 없으며 init-form의 순서를 변경할 수 없습니다. 그 이유는 Common Lisp가 함수가 부작용을 갖도록 허용하기 때문입니다. 따라서 평가 순서는 중요하며 양식 내에서 항상 왼쪽에서 오른쪽입니다. 따라서 LET에서는 init-form이 먼저 왼쪽에서 오른쪽으로 평가 된 다음 바인딩이 왼쪽 에서 오른쪽 으로 병렬로 생성됩니다. LET *에서 init-form은 평가 된 다음 왼쪽에서 오른쪽으로 순서대로 기호에 바인딩됩니다.

CLHS : 특수 연산자 LET, LET *


답변

나는 한 단계 더 사용 갈 바인드 통합하여 그 let, let*, multiple-value-bind, destructuring-bind등, 그리고 심지어 확장의를.

일반적으로 나는 “가장 약한 구조”를 사용하는 것을 좋아하지만 let& 친구들 과는 그렇지 않습니다. 그들은 단지 코드에 소음을주기 때문입니다 (주관성 경고! 그 반대를 설득 할 필요가 없습니다 …).