В Common Lisp, почему тела выражений с выражениями (если) требуют (progn)? - PullRequest
14 голосов
/ 08 февраля 2009

Является ли это лишь небольшим историческим несоответствием, оставшимся с 1950-х годов, или есть какая-то синтаксическая причина, почему множественные выражения форм (если) требуют (progn)? Почему нельзя заключить несколько выражений в набор скобок, например, с помощью (let):

   (if some-cond
     ((exp1) (exp2) (exp3)) ; multi exp "then"
     (exp4)) ; single exp "else"

Похоже, было бы тривиально написать макрос для проверки каждого тела, чтобы сначала увидеть, является ли он списком, а затем, если это так, если его первый элемент также является списком (и, следовательно, не вызовом функции), а затем оберните его подкомпоненты внутри (progn) соответственно.

Ответы [ 9 ]

17 голосов
/ 09 февраля 2009

В Common Lisp этот код:

(if t
    ((lambda (x) (+ x 5)) 10)
   20)

вернет 15. С вашим предложением, я думаю, оно увидит, что true-предложение является списком, и автоматически преобразует его в:

(if t
    (progn (lambda (x) (+ x 5)) 10)
   20)

который вернул бы 10. Это верно?

Я не уверен, что "тривиально" различать "список" и "вызов функции" в CL. Намереваетесь ли вы, чтобы это изменение не было обратно совместимым? (Новые и интересные диалекты Лисп всегда крутые, но это не Common Lisp.) Или вы можете привести пример того, что вы имеете в виду?

11 голосов
/ 08 февраля 2009

Обычный Лисп не идеален, потому что он идеален, он идеален, потому что он идеален.

Весь язык построен на 25 специальных операторах ; if - это один из них, progn - это другой.

if предоставляет только базовый механизм проверки условия, а затем переход к одному или другому кодовому адресу. progn обеспечивает основной механизм выполнения нескольких действий и возврата значения последнего.

Существует несколько макросов в языковом стандарте, которые основаны на этом - например, when, unless, cond, case.

Если вы хотите, у вас есть несколько вариантов сделать что-то похожее на то, что вы представляете: например, вы можете написать макрос ifm, который ожидает неявные progn s в качестве предложений then и else, или вы можете написать как вы сказали, чтобы он обнаружил намерение, или вы могли бы даже написать макрос чтения, чтобы добавить синтаксический сахар для progn.

6 голосов
/ 08 февраля 2009

есть ли какая-то синтаксическая причина, по которой тела с множественными выражениями (если) требуют (progn) форм?

Ответ - «да», хотя, возможно, не по той причине, которую вы ожидаете. Поскольку Common Lisp (в отличие от Scheme и других Lisps) требует funcall, ваше предложение не является двусмысленным. Даже если бы это было двусмысленно, пока ваши пользователи знают, что здесь в скобках подразумевается progn, это будет работать.

Однако никакие другие конструкции * в языке не имеют необязательных одинарных / двойных скобок. Множество конструкций имеют неявные progn с, но их синтаксис в скобках всегда одинаков.

Например, cond имеет неявное progn для каждой ветви:

(cond (test1 body1) (test2 body2) ...)

Вы не можете переключаться назад и вперед:

(cond test1 exp1 (test2 body2) t exp3)

Итак, хотя ваше предложение не является двусмысленным, оно не соответствует синтаксису остальной части языка. ТЕМ НЕ МЕНИЕ! Как вы сказали, макрос реализовать тривиально. Вы должны сделать это сами и посмотреть, хорошо ли это работает. Я мог легко ошибаться; Я довольно предвзят, так как почти весь мой Лиспинг в Схеме.

* За исключением case. Hmf. Теперь я думаю, что могут быть и другие.

4 голосов
/ 11 марта 2009

Поскольку синтаксис для IF (<- ссылка HyperSpec) определен как: </p>

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, когда нужно попробовать несколько альтернатив и когда есть несколько тогда / else-форм. IF используется, когда есть только один тест, а также форма then и else. Для других случаев есть WHEN и UNLESS. WHEN и UNLESS поддерживают только одну или ТО одну форму (никакие другие формы).

Полагаю, хорошо иметь хотя бы одну условную форму (если в этом случае), которая не содержит слоев скобок. Запись

(if (> (random 10) 5)
    (progn
       (print "high")
       :high)
    (progn
       (print "low")
       :low))

- это небольшая цена, которую нужно заплатить. Либо напишите дополнительные PROGN, либо переключитесь на вариант COND. Если ваш код действительно выиграл бы от IF с множественными формами then и else, просто напишите этот макрос (см. Выше). В Лиспе он есть, так что вы можете быть вашим собственным языковым дизайнером. Важно подумать о введении макроса: мой макрос правильный? это проверяет ошибки? стоит ли оно того? это читабельно (для других?)?

4 голосов
/ 08 февраля 2009

Не все выражения являются списками. Для (let ((a 42)) (if some-cond (a b c) (d e f))) вы не знаете, следует ли (a b c) интерпретировать как вызов функции a или как неявный прогноз.

3 голосов
/ 16 марта 2012

Обратите внимание, что в "{} языках" (C, 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 (и его двоюродные братья) являются "фигурными скобками" Лиспа. Скобки в Лиспе не являются его «фигурными скобками»!

Теперь рассмотрим Perl. Perl имеет полностью закрепленный if. Это может быть сделано в Лиспе также:

 (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))

Думаю, я мог бы жить с этим, но некоторые люди будут жаловаться на дополнительные скобки, особенно в простых случаях.

3 голосов
/ 08 февраля 2009

Если у вас нет ветви else, помогают стандартные макросы when и unless. В противном случае лучше использовать cond, если у вас есть несколько выражений в любой ветви.

3 голосов
/ 08 февраля 2009

Уже есть макросы для расширенной версии. Эта книга действительно хороша: http://www.gigamonkeys.com/book/ Ваш ответ в этой главе: http://www.gigamonkeys.com/book/macros-standard-control-constructs.html

Стандартные макросы - это когда и если.

1 голос
/ 08 февраля 2009

В Лиспе круглые скобки указывают на применение функции, а не на группирование. Что бы означало ваше выражение, если бы exp1 была функцией, которая возвращала функцию? Будет ли он вызываться с аргументами (exp2) (exp3) или нет?

...