common-lisp
Группировка форм
Поиск…
Когда нужно группировать?
В некоторых местах в Common Lisp ряд форм оценивается по порядку. Например, в теле defun или лямбда , или в теле доты . В этих случаях запись нескольких форм в порядке работает, как ожидалось. Однако в нескольких местах, таких как части then и else выражения if , допускается только одна форма. Конечно, можно захотеть на самом деле оценить несколько выражений в этих местах. Для этих ситуаций требуется какая-то неявная явная форма группировки.
Progn
Общего назначения специальный оператор progn используется для оценки ноль или более форм. Возвращается значение последней формы. Например, в следующем случае (print 'hello) оценивается (и его результат игнорируется), а затем 42 оценивается и возвращается его результат ( 42 ):
(progn
(print 'hello)
42)
;=> 42
Если в запросе нет форм, то возвращается nil :
(progn)
;=> NIL
Помимо группировки ряда форм, progn также имеет важное свойство, что если форма progn является формой верхнего уровня , то все формы внутри нее обрабатываются как формы верхнего уровня. Это может быть важно при написании макросов, которые расширяются в несколько форм, которые все должны обрабатываться как формы верхнего уровня.
Progn также ценен тем, что он возвращает все значения последней формы. Например,
(progn
(print 'hello)
(values 1 2 3))
;;=> 1, 2, 3
Напротив, некоторые выражения группировки возвращают первичное значение формы, создающей результат.
Неявные Прогнозы
Некоторые формы используют неявные прогнозы для описания их поведения. Например, когда и если макросы, которые по существу являются односторонними , если формы, описать их поведение с точки зрения неявного progn. Это означает, что такая форма, как
(when (foo-p foo)
form1
form2)
и условие (foo-p foo) истинно, тогда форма 1 и форма 2 сгруппированы так, как если бы они содержались в прогнозе . Расширение когда это макрос, по существу:
(if (foo-p foo)
(progn
form1
form2)
nil)
Prog1 и Prog2
Часто бывает полезно оценить несколько выражений и вернуть результат из первой или второй формы, а не из последней. Это легко сделать, используя let и, например:
(let ((form1-result form1))
form2
form3
;; ...
form-n-1
form-n
form1-result)
Поскольку эта форма распространена в некоторых приложениях, Common Lisp включает prog1 и prog2 , которые похожи на progn , но возвращают результат первой и второй форм, соответственно. Например:
(prog1
42
(print 'hello)
(print 'goodbye))
;; => 42
(prog2
(print 'hello)
42
(print 'goodbye))
;; => 42
Однако важное различие между prog1 / prog2 и progn заключается в том, что progn возвращает все значения последней формы, тогда как prog1 и prog2 возвращают первичное значение первой и второй форм. Например:
(progn
(print 'hello)
(values 1 2 3))
;;=> 1, 2, 3
(prog1
(values 1 2 3)
(print 'hello))
;;=> 1 ; not 1, 2, 3
Для нескольких значений с оценкой стиля prog1 вместо этого используйте multiple-value-prog1 . Нет аналогичного многозначного prog2 , но его нетрудно реализовать, если вам это нужно.
блок
Специальный блок оператора позволяет группировать несколько форм Лиспа (например, неявное progn
), а также имя, чтобы назвать этот блок. Когда формы внутри блока оцениваются, для выхода из блока можно использовать специальный оператор return-from . Например:
(block foo
(print 'hello) ; evaluated
(return-from foo)
(print 'goodbye)) ; not evaluated
;;=> NIL
return-from также может быть предоставлено возвращаемое значение:
(block foo
(print 'hello) ; evaluated
(return-from foo 42)
(print 'goodbye)) ; not evaluated
;;=> 42
Именованные блоки полезны, когда кусок кода имеет значимое имя или когда блоки вложены. В каком-то контексте важна только возможность возврата из блока раньше. В этом случае вы можете использовать nil в качестве имени блока и вернуться . Возврат точно так же, как return-from , за исключением того, что имя блока всегда равно нулю .
Примечание: закрытые формы не являются формами верхнего уровня. Это отличается от progn
, где вложенные формы с верхним уровнем progn
формы по - прежнему считаются формами верхнего уровня.
Tagbody
Для большого контроля в групповых формах специальный оператор tagbody может быть очень полезным. Формы внутри формы тегов - это либо теги go (которые являются просто символами или целыми числами), либо формы для выполнения. Внутри тега используется специальный оператор go для передачи выполнения в новое место. Этот тип программирования можно считать довольно низким, так как он допускает произвольные пути выполнения. Ниже приведен подробный пример того, как может выглядеть цикл for при реализации в качестве тега :
(let (x) ; for (x = 0; x < 5; x++) { print(hello); }
(tagbody
(setq x 0)
prologue
(unless (< x 5)
(go end))
begin
(print (list 'hello x))
epilogue
(incf x)
(go prologue)
end))
Хотя tagbody и go обычно не используются, возможно, из-за «GOTO считается вредным», но могут быть полезны при реализации сложных структур управления, таких как государственные машины. Многие итерационные конструкции также расширяются в неявный тег . Например, тело топимов указано как серия тегов и форм.
Какую форму использовать?
При написании макросов, которые расширяются в формы, которые могут включать группировку, стоит потратить некоторое время на рассмотрение того, какая структура группировки будет расширяться.
Для форм стиля определения, например, макроса define-widget , который обычно отображается как форма верхнего уровня, и что несколько defun s, defstruct s и т. Д., Обычно имеет смысл использовать progn , так что дочерние формы обрабатываются как формы верхнего уровня. Для итерационных форм чаще всего используется неявный тег .
Например, тело DOTIMES , DOLIST , и сделать каждый расшириться в неявном tagbody.
Для форм, которые определяют именованный «кусок» кода, часто используется неявный блок . Например, в то время как тело defun находится внутри неявного прогноза , этот неявный progn находится внутри блока, совместно использующего имя функции. Это означает, что return-from может использоваться для выхода из функции. Такой комп