Как `if` не оценивать все свои аргументы?

Я пытаюсь изучить и понять язык программирования Lisp на глубоком уровне. Функция + оценивает свои аргументы в прикладном порядке:

(+ 1 (+ 1 2))

(+ 1 2) будет оценен, а затем будет оценен (+ 1 3), но функция if работает по-разному:

(if (> 1 2) (not-defined 1 2) 1)

Поскольку форма (not-defined 1 2) не оценивается, программа не прерывается.

Как может тот же синтаксис привести к разной оценке аргументов? Как определена функция if, чтобы ее аргументы не оценивались?

Ответ 1

if является специальным оператором, а не обычная функция.

Это означает, что нормальное правило, состоящее в том, что элементы rest в ).

Способ, которым это реализовано в компиляторе и/или интерпретаторе, состоит в том, что вы смотрите на составную форму и решают, что с ней делать, на основе ее first элемент:

  • если это специальный оператор, он делает свою особую вещь;
  • если это макрос его макрофункция получает всю форму;
  • иначе он рассматривается как функция - даже если функция не определена.

Обратите внимание, что некоторые специальные формы могут быть определены как макросы, расширяющиеся до других специальных форм, но на самом деле должны присутствовать некоторые специальные формы.

Например, можно определить if в терминах cond:

(defmacro my-if (condition yes no) 
  `(cond (,condition ,yes)
         (t ,no)))

и наоборот (гораздо сложнее - фактически, cond - макрос, обычно расширяющийся в последовательность if s).

PS. Обратите внимание, что различие между макросами, поставляемыми системой, и специальными операторами, технически четкими и понятными (см. special-operator-p и macro-function), идеологически размыта потому что

Реализация свободна для реализации Common Lisp специального оператора как макрос. Реализация свободна для реализации любого макрооператора как специальный оператор, но только если эквивалентное определение макрос.

Ответ 2

Lisp синтаксис является регулярным, гораздо более регулярным, чем другие языки, но он все еще не является полностью регулярным: например, в

(let ((x 0))
   x)

let не является именем функции, а ((x 0)) не является плохой формой, в которой в первой позиции использовался список, который не является лямбда-формой.

Существует немало "особых случаев" (по-прежнему намного меньше, чем у других языков), когда общее правило каждого списка не является вызовом функции, а if является одним из них. Общее Lisp имеет довольно много "специальных форм" (потому что абсолютная минимальность не была точкой), но вы можете уйти, например, на диалекте схемы всего с пятью из них: if, progn, quote, lambda и set! (или шесть, если вы хотите использовать макросы).

Хотя синтаксис Lisp не является полностью однородным, базовое представление кода, однако, довольно равномерное (только списки и атомы), а единообразие и простота представления - это то, что облегчает метапрограммирование (макросы).

"Lisp не имеет синтаксиса" - это утверждение с некоторой правдой в нем, но поэтому оператор "Lisp имеет два синтаксиса": один синтаксис - это то, что использует reader для преобразования из потоков символов в s -expressions, другой синтаксис - это то, что использует компилятор/оценщик для преобразования из s-выражений в исполняемый код.

Также верно, что Lisp не имеет синтаксиса, потому что ни один из этих двух уровней не фиксирован. В отличие от других языков программирования вы можете настроить как первый шаг (используя макросы считывателя), так и второй шаг (используя макросы).

Ответ 3

sds answer хорошо отвечает на этот вопрос, но есть несколько более общих аспектов, которые, я думаю, стоит упомянуть. Как указал этот ответ и другие, if, встроен в язык как специальный оператор, потому что он действительно является своего рода примитивным. Самое главное, if не является функцией.

Тем не менее, функциональность if может быть достигнута с использованием только функций и вызова нормальной функции, где все аргументы оцениваются. Таким образом, условные выражения могут быть реализованы в лямбда-исчислении, на которых языки в семье несколько основаны, но которые не имеют условного оператора.

В исчислении лямбда можно определить true и false как функции двух аргументов. Предполагается, что аргументы являются функциями, а true вызывает первый из своих аргументов, а false вызывает второй. (Это небольшое изменение Church booleans, которое просто возвращает их первый или второй аргумент.)

 true = λ[x y].(x)
false = λ[x y].(y)

(Очевидно, это отклонение от булевых значений в Common Lisp, где nil является ложным, а что-то еще истинным.) Преимущество этого, однако, состоит в том, что мы можем использовать логическое значение для вызова одной из две функции, в зависимости от того, является ли логическое значение true или false. Рассмотрим общую форму Lisp:

(if some-condition
  then-part
  else-part)

Если бы были использованы логические значения, как определено выше, то при оценке some-condition будет выдаваться либо true, либо false, и если бы мы должны были вызвать этот результат с аргументами

(lambda () then-part)
(lambda () else-part)

тогда будет вызван только один из них, поэтому на самом деле будет оцениваться только один из then-part и else-part. В общем случае, обертывание некоторых форм в lambda является хорошим способом задержать оценку этих форм.

Сила общей макросистемы Lisp означает, что мы могли бы определить макрос if, используя типы логических элементов, описанных выше:

(defconstant true
  (lambda (x y)
    (declare (ignore y))
    (funcall x)))

(defconstant false
  (lambda (x y)
    (declare (ignore x))
    (funcall y)))

(defmacro new-if (test then &optional else)
  `(funcall ,test
            (lambda () ,then)
            (lambda () ,else)))

С помощью этих определений некоторый код выглядит следующим образом:

(new-if (member 'a '(1 2 3))
  (print "it a member")
  (print "it not a member"))))

расширяется до этого:

(FUNCALL (MEMBER 'A '(1 2 3))                     ; assuming MEMBER were rewritten 
         (LAMBDA () (PRINT "it a member"))      ; to return `true` or `false`
         (LAMBDA () (PRINT "it not a member")))

В общем случае, если существует некоторая форма, и некоторые из аргументов не оцениваются, форма (car) является либо специальным оператором Lisp, либо макросом. Если вам нужно написать функцию, в которой будут оцениваться аргументы, но вы хотите, чтобы некоторые формы не оценивались, вы можете их обернуть в выражениях lambda, и ваша функция вызовет эти анонимные функции условно.

Это возможный способ реализации if, если у вас его еще нет на этом языке. Конечно, современное компьютерное оборудование не основано на интерпретаторе лямбда-исчисления, а скорее на процессорах с инструкциями по тестированию и прыжкам, поэтому более эффективно для языка предоставлять if примитив и компилировать до соответствующих машинных инструкций.

Ответ 4

Не было бы смысла делать это. Пример: (if (ask-user-should-i-quit) (quit) (continue)). Должно ли это выйти, даже если пользователь не хочет?

IF не является функцией в Lisp. Это специальный встроенный оператор. Lisp несколько встроенных специальных операторов. См. Специальные формы. Это не функции.

Ответ 5

Аргументы не оцениваются как функции, потому что if - специальный оператор. Специальные операторы могут быть оценены произвольным образом, поэтому их называют специальными.

Рассмотрим, например,

(if (not (= x 0))
    (/ y x))

Если деление всегда оценивалось, может быть деление на нулевую ошибку, которая явно не была предназначена.

Ответ 6

Если это не функция, это особый вид. Если вы хотели реализовать подобную функциональность самостоятельно, вы могли бы сделать это, указав макрос, а не функцию.

Этот ответ относится к Common Lisp, но он, вероятно, будет таким же для большинства других Lisps (хотя в некоторых if может быть макросом, а не специальной формой).