(Я начал писать это до ответа Сильвестра, что, как мне кажется, в основном лучше.)
Критическое различие между языками семейства Лисп и многими другими языками состоит в том, что языки семейства Лисп являются «языками выражения».Технически это означает, что в языках, таких как (скажем) C или Python, существует два вида конструкций:
- выражения, которые имеют значения;
- выражения, которые не имеют;
В то время как в языках семейства Lisp есть одна вещь: выражения, которые имеют значения.В результате этого языки семейства Lisp иногда называют «языками выражений».
Это имеет огромное значение, если вы хотите писать функции, которые являются вещами, которые возвращают значения (вызов функции - это выражение в другомслова).
Обычные языки (например, Python)
В языке, который не является языком выражений, тогда, если вы определяете функцию и попадаете в середину некоторой конструкции, котораяявляется оператором, и вы хотите вернуть значение, вы должны использовать некоторую специальную магическую конструкцию, часто называемую return
, чтобы сделать это.Таким образом, в Python, где условные выражения являются операторами, вы можете написать:
def fib(n):
if n < 2:
return n
else:
return fib(n - 1) + fib(n - 2)
И на самом деле у вас есть для использования return
, поскольку тело определения функции в Python - это последовательностьоператоров, поэтому для возврата любого вида значения вам нужно использовать return
.
На самом деле, Python (и C, и Java & c & c) имеют специальную форму условного выражения, которое является выражением: в Python это выглядит так:
def fib(n):
return n if n < 2 else (fib(n - 1) + fib(n - 2)
По-другому выглядит в C, но делает то же самое.
Но вам все еще нужно это надоедливое return
(ОК, только один изих сейчас), и это обнаруживает еще одну особенность таких языков: если какое-то место в синтаксисе требует оператора, вам, как правило, нужно иметь оператор там, или если вы можете поместить выражение там, его значение просто отбрасывается.Таким образом, вы можете попробовать что-то вроде этого:
def fib(n):
n if n < 2 else (fib(n - 1) + fib(n - 2)
И это синтаксически нормально - выражение превращается в оператор - но оно терпит неудачу во время выполнения, потому что функция больше не возвращает полезное значение.В Python вы можете обойти это, если хотите, чтобы люди вас ненавидели:
fib = lambda n: n if n < 2 else fib(n - 1) + fib(n - 2)
Люди Python будут ненавидеть вас, если вы сделаете это, и это также бесполезно, потому что Python только для lambda
1041 * принимает выражения, поэтому то, что вы можете написать, ограничено.
Lisp
Lisp не имеет ничего из этого: в Lisp все является выражением и, следовательно, все имеетзначение , вам просто нужно знать, откуда оно.Еще есть return
(в CL, во всяком случае), но вам нужно использовать его гораздо реже.
Но, конечно, люди часто хотят писать программы, которые выглядят так: «Сделай это, затем сделай это,затем сделайте это », где большая часть работы выполняется для побочного эффекта, поэтому Лиспс обычно имеет какую-то конструкцию последовательности, которая позволяет вам просто иметь кучу выражений одно за другим, все, кроме (обычно) одного из которыхполучить оценку на побочный эффект.В CL самая распространенная секвенирующая конструкция называется progn
(по историческим причинам).(progn ...)
является выражением, составленным из других выражений, а его значением является значение последнего выражения в его теле.
progn
настолько полезно, что группа других конструкций имеет «неявное progn
»в них.Два примера - это определения функций (тело defun
является неявным progn
) и cond
(тело cond
-класса является неявным `progn).
Ваша функция
Вот ваша функция (первая версия) с указанием различных ее частей
(defun fn (n)
;; the body of fn is an implicit progn with one expression, so
;; do this and return its value
(cond
;; the value of cond is the value of the selected clause, or nil
((zerop n)
;; the body of this cond clause is an implicit progn with on
;; expression so do this and ... it never returns
(return-from fn nil))
(t
;; the body of this cond clause is an implicit progn with two expressions, so
;; do this for side-effect
(write-line "Hello World")
;; then do this and return its value
(fn (- n 1)))))
Вот вторая версия
(defun fn (n)
;; the body of fn is an implicit progn with one expression, so
;; do this and return its value
(cond
;; the value of cond is the value of the selected clause, or nil
((zerop n)
;; the body of this cond clause is an implicit progn with on
;; expression so do this and ... it never returns
(return-from fn nil))
(t
;; the body of this cond clause is an implicit progn with two expressions, so
;; do this for side-effect
(fn (- n 1))
;; then do this and return its value
(write-line "Hello World"))))
Итак, вы можете видеть, что здесь происходит: в первой версии возвращаемое значение равно либо nil
, либо значению рекурсивного вызова (также nil
).Во второй версии возвращаемое значение равно либо nil
, либо тому, что возвращает write-line
.И получается, что write-line
возвращает значение его аргумента, так что это то, что вы получаете, если вы называете его с целым числом больше нуля.
Почему в Lisp есть return-from
вообще?
Одна вещь, которая должна быть сразу понятна из всего этого языка выражений, заключается в том, что вам вряд ли когда-нибудь понадобится явно что-то возвращать в Лиспе: у вас просто есть выражение, которое вычисляет желаемое значение.Но есть два хороших использования (которые, возможно, на самом деле одинаковые) явных возвратов.
Во-первых, иногда вы делаете какой-то большой поиск чего-либо в виде группы вложенных циклов и в некоторыхто, что вы просто хотите сказать: «Хорошо, нашел это, вот ответ».Вы можете сделать это одним из двух способов: вы можете аккуратно структурировать свои циклы так, чтобы, как только вы нашли то, что вы хотите, они все хорошо заканчивались и значение возвращалось обратно, или вы можете просто сказать «вот ответ».Последняя вещь - то, что делает return-from
: она просто говорит: «Я закончил, осторожно размотайте стек и верните это»:
(defun big-complicated-search (l m n)
(dotimes (i l)
(dotimes (j m)
(dotimes (k n)
(let ((it (something-involving i j k l m n)))
(when (interesting-p it)
(return-from big-complicated-search it)))))))
И return-from
делает это в правомway :
(defun big-complicated-file-search (file1 file2)
(with-open-file (f1 file1)
(with-open-file (f2 file2)
...
(when ...
(return-from big-complicated-search found)))))
Когда вы вызываете это, и когда вещь обнаружена, return-from
будет гарантировать, что два файла, которые вы открыли, правильно закрыты.
во-вторых, это почти то же самое, что иногда нужно просто сдаться, и return-from
- хороший способ сделать это: он сразу возвращается, занимается очисткой (см. выше) и, как правило, неплохоспособ сказать «хорошо, я сдаюсь сейчас».На первый взгляд это похоже на то, что вы бы сделали с какой-то системой обработки исключений, но на самом деле есть два критических различия:
- в системе обработки исключений (которая, конечно, есть в CL)), вам нужно создать какое-то исключение, чтобы создать что-то;
- системы обработки исключений динамические не лексические : если вы вызываете исключениезатем то, что нужно для его обработки, используется для динамического увеличения стека: это означает, что вы зависите от любого, кто мешает обработчику, и он также обычно довольно медленный.
Наконец, механизм исключительного возврата через обработку ошибок просто ужасен.