В 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 имеет это, так что вы можете быть вашим собственным дизайнером языка. Важно хотя подумать о введении макроса: правильно ли мой макрос? он проверяет ошибки? стоит ли оно того? это читаемо (для других?)?

Ответ 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) или нет?