Во-первых, обратите внимание, что синтаксис let
состоит из двух частей, каждая из которых может иметь ноль или более элементов. Он связывает ноль или более переменных, и оценивают ноль или более форм.
Все такие формы Lisp создают проблему: если элементы представлены в виде плоского списка, возникает двусмысленность: мы не знаем, где заканчивается один список, а начинается другой!
(let <var0> <var1> ... <form0> <form1> ...)
Например, предположим, что у нас было это:
(let (a 1) (b 2) (print a) (list b))
Что такое (print a)
: является ли переменная print
привязанной к a
? Или это form0 для оценки?
Следовательно, подобные конструкции Лиспа почти всегда разрабатываются таким образом, что один из двух списков представляет собой один объект или, возможно, оба. Другими словами: одна из этих возможностей:
(let <var0> <var1> ... (<form0> <form1> ...))
(let (<var0> <var1> ...) (<form0> <form1> ...))
(let (<var0> <var1> ...) <form0> <form1> ...)
Традиционный Лисп следовал третьей идее, изложенной выше, при разработке let . Эта идея имеет то преимущество, что к элементам формы легко и эффективно получить доступ в интерпретаторе, компиляторе или любом коде, который обрабатывает код. Учитывая объект L , представляющий синтаксис let , переменные легко извлекаются как (cadr L) , а тело формируется как (cddr L) .
Теперь, в этом выборе дизайна, все еще есть немного свободы дизайна. Переменные могут иметь структуру, аналогичную списку свойств:
(let (a 1 b 2 c 3) ...)
, или они могут быть заключены:
(let ((a 1) (b 2) (c 3)) ...)
Вторая форма традиционная. В диалекте Ar c, разработанном Лиспом Полом Грэмом, появляется прежний синтаксис.
В традиционной форме больше скобок. Тем не менее, он позволяет опустить формы инициализации: то есть, если желаемое начальное значение переменной должно быть nil
, вместо записи (a nil)
, вы можете просто написать a
:
;; These two are equivalent:
(let ((a nil) (b nil) (c)) ...)
(let (a b c) ...)
Это полезное сокращение в контексте традиционного Лиспа, который использует символ nil
для логического false и для пустого списка. Мы кратко определили три переменные, которые по умолчанию являются либо пустыми списками, либо ложными логическими значениями.
По сути, мы можем рассматривать традиционный let
как предназначенный главным образом для связывания простого списка переменных, как в (let (a b c) ...)
, который по умолчанию nil
. Затем этот синтаксис будет расширен для поддержки начальных значений путем необязательной замены переменной var
на пару (var init)
, где init
- это выражение, вычисленное для указания его начального значения.
В любом случае, благодаря макросам, вы можете иметь любой синтаксис связывания. В более чем одной программе я видел макрос let1
, который связывает только одну переменную и не содержит скобок. Он используется так:
(let1 x 2 (+ x 2)) -> 4
В Common Lisp мы очень легко можем определить let1
следующим образом:
(defmacro let1 (var init &rest body)
`(let ((,var ,init)) ,@body))
Если мы ограничим let1
, чтобы иметь одно- тело формы, затем мы можем написать выражение с навязчиво несколькими круглыми скобками;
(let1 x 2 + x 2) -> 4
Вот это:
(defmacro let1 (var init &rest form)
`(let ((,var ,init)) (,@form)))