86

LET と LET* (並列バインディングと順次バインディング) の違いを理解しており、理論的な問題としては完全に理にかなっています。しかし、実際に LET が必要になったことはありますか? 私が最近見たすべての Lisp コードでは、すべての LET を変更せずに LET* に置き換えることができました。

編集: OK、私はなぜ誰かが LET* をおそらくマクロとして発明したのか理解しています。私の質問は、LET* が存在することを考えると、LET が存続する理由はあるのでしょうか? LET* が普通の LET と同様に機能しない実際の Lisp コードを書いたことがありますか?

私は効率の議論を支持しません。まず、LET* を LET と同じくらい効率的なものにコンパイルできるケースを認識することは、それほど難しいことではないようです。第 2 に、CL 仕様には、効率性を考慮して設計されたとはまったく思えないものがたくさんあります。(型宣言を伴う LOOP を最後に見たのはいつですか? それらを理解するのは非常に難しいので、私はそれらが使用されているのを見たことがありません。) 1980 年代後半の Dick Gabriel のベンチマークの前は、CL実に遅かったです。

これは下位互換性のもう 1 つのケースのようです。賢明なことに、LET のような基本的なものを壊す危険を冒す人は誰もいませんでした。これは私の予感でしたが、LET が LET* よりもばかばかしいほど簡単に多くのことを行った、私が見逃していたばかばかしいほど単純なケースを誰も持っていないと聞いて安心しました。

4

16 に答える 16

90

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))

i上記では、変数が であると宣言されていますfixnum

Richard P. Gabriel は 1985 年に Lisp ベンチマークに関する本を出版しましたが、当時これらのベンチマークは非 CL Lisp でも使用されていました。Common Lisp 自体は 1985 年には真新しいものでした。この言語について説明したCLtL1の本が 1984 年に出版されたばかりでした。当時、実装があまり最適化されていなかったのも不思議ではありません。実装された最適化は、基本的に以前の実装 (MacLisp など) と同じ (またはそれ以下) でした。

しかし、 forと主な違いは、LETバインディング句が互いに独立しているため、LET*コードを使用する方が人間にとって理解しやすいことです。特に、左から右への評価を利用するのは悪いスタイルであるためです (変数を側に設定しない)LET効果)。

于 2009-02-25T21:03:43.957 に答える
39

LETは必要ありませんが、通常は必要です

LET は、トリッキーなことは何もせずに、標準の並列バインディングを行っているだけであることを示唆しています。LET* は、コンパイラーに制限を課し、順次バインディングが必要な理由があることをユーザーに示唆します。スタイルに関しては、LET* による追加の制限が必要ない場合は、LET の方が優れています。

LET* よりも LET を使用する方が効率的です (コンパイラ、オプティマイザなどによって異なります)。

  • 並列バインディングは並列で実行できます(ただし、実際にこれを行う LISP システムがあるかどうかはわかりません。また、init フォームは引き続き順次実行する必要があります)。
  • 並列バインディングは、すべてのバインディングに対して単一の新しい環境 (スコープ) を作成します。シーケンシャル バインディングでは、バインディングごとに新しいネストされた環境が作成されます。並列バインディングはメモリ使用量が少なく、変数検索が高速です。

(上記の箇条書きは、別の LISP 方言である Scheme に適用されます。clisp は異なる場合があります。)

于 2009-02-17T00:21:23.913 に答える
29

私は不自然な例を持っています。この結果を比較します。

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

これを実行した結果:

(print (let ((c 1))
         (let* ((c 2)
                (a (+ c 1)))
           a)))
于 2009-02-16T23:21:18.690 に答える
11

LISP では、可能な限り弱い構造を使用したいという要望がよくあります。たとえば、比較対象の項目が数値であることがわかっている場合、一部のスタイル ガイドでは=、 ではなくを使用するように指示されます。eql多くの場合、コンピューターを効率的にプログラムするのではなく、意味を指定することを目的としています。

ただし、より強力な構文を使用せず、意味することだけを言うことで、実際の効率が向上する可能性があります。を使用して初期化を行う場合LET、それらは並列で実行できますが、LET*初期化は順次実行する必要があります。実装が実際にそれを行うかどうかはわかりませんが、将来的にはうまくいくかもしれません。

于 2009-02-16T23:18:38.777 に答える
10

LET と LET* の Common List の主な違いは、LET のシンボルは並列にバインドされ、LET* のシンボルは順次バインドされることです。LET を使用しても、init-forms を並行して実行することはできず、init-forms の順序を変更することもできません。その理由は、Common Lisp では関数に副作用を持たせることができるからです。したがって、評価の順序は重要であり、フォーム内では常に左から右になります。したがって、LET では、最初に init-forms が左から右に評価され、次にバインディングが左から右に並列に作成されます。LET* では、init-form が評価され、左から右の順にシンボルにバインドされます。

CLHS: 特殊オペレーター LET、LET*

于 2009-02-18T21:46:33.720 に答える
9

私は最近、2つの引数の関数を書いていました。どちらの引数が大きいかがわかっている場合、アルゴリズムは最も明確に表現されます。

(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*たら、誤って同じ値に設定aしていたでしょう。b

于 2012-02-27T02:13:28.840 に答える
8

私はさらに一歩進んで、、、などを統合するbindを使用し、さらに拡張可能です。letlet*multiple-value-binddestructuring-bind

一般的に、私は「最も弱い構造」を使用するのが好きですがlet、コードにノイズを与えるだけなので、 & 友人とはそうではありません (主観的な警告! 反対のことを私に納得させる必要はありません...)

于 2009-02-17T00:31:49.223 に答える
4
(let ((list (cdr list))
      (pivot (car list)))
  ;quicksort
 )

もちろん、これはうまくいきます:

(let* ((rest (cdr list))
       (pivot (car list)))
  ;quicksort
 )

この:

(let* ((pivot (car list))
       (list (cdr list)))
  ;quicksort
 )

しかし、重要なのはその考えです。

于 2010-05-19T08:54:30.993 に答える
4

おそらくlet、コンパイラを使用することで、おそらくスペースや速度を改善するために、コードを並べ替える柔軟性が向上します。

スタイル的には、並列バインディングを使用すると、バインディングがグループ化されるという意図が示されます。これは、動的バインディングを保持するために使用されることがあります。

(let ((*PRINT-LEVEL* *PRINT-LEVEL*)
      (*PRINT-LENGTH* *PRINT-LENGTH*))
  (call-functions that muck with the above dynamic variables)) 
于 2009-02-16T23:21:11.437 に答える
2

OPは「実際に必要なLET」を尋ねますか?

Common Lispが作成されたとき、さまざまな方言で既存のLispコードのボートロードがありました。Common Lispを設計した人々が受け入れた簡単な説明は、共通の基盤を提供するLispの方言を作成することでした。彼らは、既存のコードをCommonLispに簡単かつ魅力的に移植できるようにするために「必要」でした。LETまたはLET*を言語から除外することは、他のいくつかの美徳に役立つ可能性がありますが、その重要な目標を無視していたでしょう。

LET *よりもLETを使用します。これは、データフローがどのように展開されているかを読者に伝えるためです。私のコードでは、少なくともLET *が表示された場合、早期にバインドされた値が後のバインドで使用されることがわかります。私はそれをするのに「必要」ですか、いいえ。しかし、私はそれが役立つと思います。とはいえ、デフォルトでLET *に設定されているコードと、作成者が本当に望んでいたことを示すLETシグナルの出現を読んだことはめったにありません。つまり、たとえば2つの変数の意味を交換します。

(let ((good bad)
     (bad good)
...)

「実際の必要性」に近づく議論の余地のあるシナリオがあります。これはマクロで発生します。このマクロ:

(defmacro M1 (a b c)
 `(let ((a ,a)
        (b ,b)
        (c ,c))
    (f a b c)))

よりもうまく機能します

(defmacro M2 (a b c)
  `(let* ((a ,a)
          (b ,b)
          (c ,c))
    (f a b c)))

(M2 cba)はうまくいかないので。しかし、これらのマクロはさまざまな理由でかなりずさんです。そのため、「実際の必要性」の議論が損なわれます。

于 2013-02-23T03:04:29.153 に答える
2

平行綴じを使用すると、

(setq my-pi 3.1415)

(let ((my-pi 3) (old-pi my-pi))
     (list my-pi old-pi))
=> (3 3.1415)

そして、Let* シリアル バインディングを使用すると、

(setq my-pi 3.1415)

(let* ((my-pi 3) (old-pi my-pi))
     (list my-pi old-pi))
=> (3 3)
于 2010-09-28T22:02:09.813 に答える
1

Rainer Joswigの答えに加えて、純粋主義者または理論的な観点から。Let と Let* は 2 つのプログラミング パラダイムを表しています。それぞれ機能的および順次。

Let の代わりに Let* を使い続ける必要がある理由については、まあ、あなたは私が家に帰って純粋な関数型言語で考えるのを楽しんでいます。

于 2010-08-11T22:59:17.630 に答える
-1

特に LET* が必要でない限り、私は主に LET を使用しますが、通常はさまざまな (通常は複雑な) デフォルト設定を行うときに、明示的に LET を必要とするコードを書くこともあります。残念ながら、手元に便利なコード例がありません。

于 2009-02-20T14:33:23.280 に答える
-1

letf と letf* をもう一度書き直したいと思う人はいますか? unwind-protect 呼び出しの数?

シーケンシャル バインディングの最適化が容易になります。

多分それはenvに影響しますか?

動的エクステントでの継続を許可しますか?

時々 (let (xyz) (setq z 0 y 1 x (+ (setq x 1) (prog1 (+ xy) (setq x (1- x)))))) (values () ))

[ うまくいっていると思います ] ポイントは、単純な方が読みやすい場合があるということです。

于 2011-12-24T04:52:53.187 に答える