Вероятно, лучше сначала посмотреть на let
против let*
. Если вы это понимаете, то из этого следует do
по сравнению с do*
, за исключением дополнительного рассмотрения пошаговых форм.
Common Lisp - это строго оцениваемый язык. И в let
, и в let*
переменные init-forms вычисляются слева направо. Разница в объеме и привязке. Под let
все формы init оцениваются в области, в которой ни одна из переменных не видна, тогда как под let*
формы оцениваются в среде, в которой все предыдущие переменные видимый. Во-вторых, поскольку под let*
предыдущие переменные видны, их значения также устанавливаются.
Используя let
, мы можем создать область видимости, в которой значения двух переменных отображаются местами:
(let ((x y)
(y x))
...)
Сначала вычисляются инициализирующие выражения y
и x
в указанном порядке, а затем новые значения x
и y
привязываются к полученным значениям, что делает это возможным.
С другой стороны:
(let* ((a 1)
(b (+ a 2)))
Здесь вычисляется 1
и связывается a
. Этот a
затем становится видимым для выражения (+ a 2)
, значение которого вычисляется и связывается с b
.
Теперь, с do
/ do*
. Эти макросы перед первой итерацией выполняют привязку переменных точно так же, как let
/ let*
. При связывании переменных разница между do
и do*
точно такая же, как между let
и let*
.
Макросы do
/ do*
также имеют ступенчатые формы, которые дают следующее значение для соответствующих им итерационных переменных. Все эти пошаговые формы входят в область видимости всех переменных, независимо от того, является ли макрооператор do
или do*
. Независимо от того, используете ли вы do
или do*
, вы можете ссылаться на любую переменную в любой пошаговой форме. Разница в том, когда происходит задание. Под do
все формы шагов оцениваются сверху вниз, а затем их соответствующим переменным присваиваются новые значения для следующей итерации. Под do*
поведение будет «назначать как вы go». При оценке каждой ступенчатой формы присваивается соответствующая переменная. Следовательно, в do
, когда ступенчатая форма ссылается на любую переменную, она обращается к ее значению из предыдущей итерации. В do*
, если ступенчатая форма ссылается на лексически более раннюю переменную, она получает новое значение. Если он ссылается на лексически более позднюю переменную, он все еще видит старое значение из предыдущей итерации.
Мы должны подчеркнуть, что, хотя let
и do
имеют некоторое «параллельное» поведение, в некотором смысле, параллельной оценки нет. Все видимые эффекты выполняются слева направо. Кажется, что параллельно происходит то, что переменные возникают или им присваиваются новые значения в новой итерации. Но это только параллель в том смысле, что программа не может наблюдать промежуточный прогресс. Например, передача аргументов функции в функцию также «параллельна»; программа не отслеживает состояние, в котором вызов функции частично выполняется, и передана только половина аргументов.