В Common Lisp, почему нужны тела многоэкпозиции операторов if (progn)?
Является ли это лишь немного историческим крутом, оставшимся с 1950-х годов, или есть какая-то причина синтаксически, почему тела множественного выражения (if) требуют (progn)? Почему вы не можете обернуть несколько выражений в набор круглых скобок, например, с помощью (let):
(if some-cond
((exp1) (exp2) (exp3)) ; multi exp "then"
(exp4)) ; single exp "else"
Кажется, было бы тривиально написать макрос, чтобы проверить каждое тело, чтобы увидеть его первым, если это список, а затем, если он есть, если его первый элемент также является списком (и, следовательно, не вызовом функции), а затем оберните свои подкомпоненты внутри (progn) соответственно.
Ответы
Ответ 1
В Common Lisp этот код:
(if t
((lambda (x) (+ x 5)) 10)
20)
вернется 15. С вашим предложением я думаю, что он увидит, что предложение true - это список и автоматически преобразует его в:
(if t
(progn (lambda (x) (+ x 5)) 10)
20)
который вернет 10. Правильно ли это?
Я не уверен, что это "тривиально", чтобы различать "список" и "вызов функции" в CL. Намерены ли вы, чтобы это изменение не было обратно совместимо? (Новые и интересные диалекты Lisp всегда круты, но тогда это не Common Lisp.) Или вы можете привести пример того, что вы имеете в виду?
Ответ 2
Общий Lisp не идеален, потому что он совершенен, он идеален, потому что он является совершенным.
Весь язык построен на 25 специальных операторах; if
является одним из тех, progn
является другим.
if
предоставляет только базовый механизм проверки состояния, а затем переход к одному или другому коду. progn
обеспечивает основной механизм выполнения нескольких действий и возврата значения последнего.
В стандарте языка есть несколько макросов, которые основываются на этом - например, when
, unless
, cond
, case
.
Если вы хотите, у вас есть несколько вариантов сделать что-то вроде того, что вы себе представляете: для одного вы могли бы написать макрос ifm
, который ожидает неявный progn
как then- и else-clauses, или вы можете написать его как вы сказали, что он обнаруживает намерение, или вы даже можете написать прочитанный макрос, чтобы добавить синтаксический сахар для progn
.
Ответ 3
Есть ли какая-то причина синтаксически, почему тела мультивыражения форм (if) требуют (progn)?
Ответ "да", хотя, возможно, и не по той причине, которую вы ожидаете. Поскольку Common Lisp (в отличие от Scheme и других Lisps) требует funcall
, ваше предложение не является двусмысленным. Даже если это было неоднозначно, если ваши пользователи знают, что круглые скобки подразумевают progn
здесь, это сработает.
Однако никакие другие конструкции * в языке не имеют необязательных одиночных/двойных круглых скобок. Множество конструкций имеют неявные progn
s, но их вставной синтаксис всегда один и тот же.
Например, cond
имеет неявный progn
для каждой ветки:
(cond (test1 body1) (test2 body2) ...)
Вы не можете переключаться взад и вперед:
(cond test1 exp1 (test2 body2) t exp3)
Итак, хотя ваше предложение не является двусмысленным, оно не соответствует синтаксису остальной части языка. ОДНАКО! Как вы сказали, макрос тривиален для реализации. Вы должны сделать это сами и посмотреть, хорошо ли это работает. Я легко ошибаюсь; Я довольно предвзятый, так как почти все мои Lisping находятся в Scheme.
* За исключением case
. Hmf. Теперь я думаю, что могут быть и другие.
Ответ 4
Не все выражения являются списками. Для (let ((a 42)) (if some-cond (a b c) (d e f)))
вы не знаете, следует ли интерпретировать (a b c)
как вызов функции a
или как неявный прогноз.
Ответ 5
Поскольку синтаксис IF (< - ссылка HyperSpec) определяется как:
if test-form then-form [else-form] => result*
Нет начальных или конечных маркеров. Существует THEN-FORM, а не THEN-FORM *.
PROGN - это механизм определения последовательности форм, где формы выполняются слева направо и возвращаются значения последней формы.
Он мог быть определен следующим образом:
my-if test-form (then-form*) [(else-form*)] => result*
(defmacro my-if (test then &optional else)
(assert (and (listp then) (listp else)) (then else))
`(if ,test (progn ,@then) (progn ,@else)))
(my-if (> (random 10) 5)
((print "high")
:high)
((print "low")
:low))
Ну, уже есть конструкция, которая поддерживает несколько форм: COND.
(cond ((> (random 10) 5)
(print "high")
:high)
(t
(print "low")
:low))
Типичный стиль - использовать COND, когда нужно попробовать несколько альтернатив и когда есть несколько then/else-forms. IF используется, когда есть только один тест и как форма then, так и else. Для других случаев есть КОГДА и ЕСЛИ. WHEN и UNLESS поддерживают только одну или THEN-формы (нет другой формы).
Я думаю, хорошо иметь хотя бы одну условную форму (в этом случае IF), которая приходит без добавленных слоев круглых скобок. Запись
(if (> (random 10) 5)
(progn
(print "high")
:high)
(progn
(print "low")
:low))
- это небольшая цена для оплаты. Либо напишите дополнительные PROGN, либо переключитесь на вариант COND.
Если ваш код действительно выиграет от IF с несколькими формами then и else, просто напишите этот макрос (см. Выше). Lisp имеет это, так что вы можете быть вашим собственным дизайнером языка. Важно хотя подумать о введении макроса: правильно ли мой макрос? он проверяет ошибки? стоит ли оно того? это читаемо (для других?)?
Ответ 6
Уже есть макросы для расширенной версии. Эта книга действительно хороша: http://www.gigamonkeys.com/book/ Ваш ответ в этой главе: http://www.gigamonkeys.com/book/macros-standard-control-constructs.html
Стандартные макросы - это когда и если.
Ответ 7
Если у вас нет ветки "else", обратитесь к стандартным макросам when
и unless
. В противном случае лучше использовать cond
, если у вас есть несколько выражений в любой ветке.
Ответ 8
Обратите внимание, что в "{} языках" (C, С++, Java...) у вас есть PROGN в виде составного оператора в фигурных скобках.
Итак, обратите внимание на эти эквивалентности/аналогии:
(if antecedent consequent alternative)
if (antecedent) consequent; else alternative;
(if antecedent (progn consequent1 consequent2) alternative)
if (antecedent) { consequent1; consequent2; } else alternative;
Оператор PROGN
(и его кузены) Lisp "фигурные скобки". Скобки в Lisp не являются его "скобками"!
Теперь рассмотрим Perl. Perl имеет полностью закрепленный if
. Это также можно сделать в Lisp:
(if antecedent (consequent1 consequent2 ...) (alternative1 alternative2 ...))
например:.
(if (< foo 0)
((format t "foo is less than zero")
(- foo))
((format t "foo is not less than zero")
foo))
Я мог бы жить с этим, я думаю, но некоторые люди будут жаловаться на дополнительные круглые скобки, особенно в простых случаях.
Ответ 9
В Lisp круглые скобки указывают на применение функции, а не на группировку. Что означало бы ваше выражение, если exp1
была функцией, которая возвращала функцию? Будет ли он вызываться с аргументами (exp2) (exp3)
или нет?