При цьому let
всі вирази, що ініціалізують змінну, бачать абсолютно те саме лексичне середовище: те, що оточує let
. Якщо випадково ці вирази фіксують лексичні закриття, усі вони можуть мати спільний об’єкт середовища.
При цьому let*
кожен ініціалізуючий вираз знаходиться в іншому середовищі. Для кожного наступного виразу середовище повинно бути розширене, щоб створити нове. Принаймні в абстрактній семантиці, якщо закриття фіксується, вони мають різні об'єкти навколишнього середовища.
let*
Повинно бути добре оптимізовано , щоб згорнути непотрібні розширення середовища для того , щоб підходять в якості заміни для повсякденної let
. Повинен бути компілятор, який працює, форми якого отримують доступ до чого, а потім перетворює всі незалежні у більші комбіновані let
.
(Це справедливо, навіть якщо let*
це просто макрооператор, який випромінює каскадні let
форми; оптимізація проводиться на цих каскадних let
s).
Ви не можете реалізувати 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
versus let*
, оскільки let*
це явно не абстракція вищого рівня щодо let
. Вони приблизно на "рівному рівні". За допомогою let*
ви можете ввести помилку, яка вирішується простим переходом на let
. І навпаки . let*
насправді є лише м’яким синтаксичним цукром для візуально руйнуючогося let
гніздування, а не значною новою абстракцією.