の下ではlet
、すべての変数初期化式は、まったく同じ字句環境、つまりを囲む環境を参照しlet
ます。これらの式がたまたま字句クロージャをキャプチャする場合、それらはすべて同じ環境オブジェクトを共有できます。
の下ではlet*
、すべての初期化式は異なる環境にあります。連続する式ごとに、環境を拡張して新しい式を作成する必要があります。少なくとも抽象的なセマンティクスでは、クロージャがキャプチャされた場合、それらは異なる環境オブジェクトを持ちます。
のlet*
日常的な代替として適しているためには、不要な環境拡張を折りたたむように十分に最適化する必要がありますlet
。どのフォームが何にアクセスしているかを動作し、独立したフォームをすべてより大きな結合されたものに変換するコンパイラが必要let
です。
(これはlet*
、カスケードlet
形式を発行する単なるマクロ演算子であっても当てはまります。最適化はそれらのカスケード形式で行われlet
ます)。
適切なスコープの欠如が明らかになるため、初期化を行うための隠れた変数割り当てを使用しlet*
て、単一のナイーブとして実装することはできませんlet
。
(let* ((a (+ 2 b))
(b (+ 3 a)))
forms)
これがになったら
(let (a b)
(setf a (+ 2 b)
b (+ 3 a))
forms)
この場合は機能しません。内側b
が外側をシャドウしているb
ので、2を追加することになりますnil
ます。この種の変換は、これらすべての変数の名前をアルファ名変更すると実行できます。その後、環境は適切に平坦化されます。
(let (#:g01 #:g02)
(setf #:g01 (+ 2 b)
#:g02 (+ 3 #:g01))
alpha-renamed-forms)
そのためには、デバッグサポートを検討する必要があります。プログラマーがデバッガーを使用してこの字句スコープにステップインする場合、の#:g01
代わりに処理する必要がありa
ますか。
だから基本的に、 let*
は、を実行するために、またlet
それがに減少する可能性がある場合に、適切に最適化する必要がある複雑な構造let
です。
それだけでは支持することを正当化しないでしょう let
するlet*
。優れたコンパイラがあるとしましょう。let*
いつも使ってみませんか?
一般原則として、エラーが発生しやすい低レベルの構成よりも、生産性を高めてミスを減らす高レベルの構成を優先し、高レベルの構成の適切な実装に可能な限り依存して、犠牲にする必要がほとんどないようにする必要があります。パフォーマンスのためのそれらの使用。そもそも私たちがLispのような言語で働いているのはそのためです。
にlet
比べて明らかに高レベルの抽象化ではないlet*
ため、この推論は対にうまく適用されません。それらは「等しいレベル」についてです。を使用すると、に切り替えるだけで解決されるバグを導入できます。そして、その逆。実際には、ネストを視覚的に折りたたむための穏やかな構文糖衣であり、重要な新しい抽象化ではありません。let*
let
let*
let
let*
let