7.10 Возврат и обработка нескольких значений

Обычно результатом вызова Lisp’овой функции является один Lisp’овый объект. Однако, иногда для функции удобно вычислить несколько объектов и вернуть их. Common Lisp представляет механизм для прямой обработки нескольких значений. Механизм удобнее и эффективнее, чем исполнение трюков со списками или глобальными переменными.

7.10.1 Конструкции для обработки нескольких значений

Обычно не используется несколько значений. Для возврата и обработки нескольких значений требуются операторы. Если вызывающий функцию код не требует нескольких значений, однако вызываемая функция возвращает их несколько, то для кода берётся только первое значение. Все оставшиеся значения игнорируются. Если вызываемая функция возвращает ноль значений, вызывающий код в качестве значения получает nil.

values — это главный примитив для возврата нескольких значений. Он принимает любое количество аргументов и возвращает столько же значений. Если последняя форма тела функции является values с тремя аргументами, то вызов такой функции вернёт три значения. Другие операторы также возвращают несколько значений, но они могут быть описаны в терминах values. Некоторые встроенные Common Lisp функции, такая как floor, возвращают несколько значений.

Операторы, обрабатывающие несколько значений, представлены ниже:

multiple-value-list
multiple-value-call
multiple-value-prog1
multiple-value-bind
multiple-value-setq

Они задают форму для вычисления и указывают куда поместить значения возвращаемые данной формой.

[Функция] values &rest args

Все аргументы в таком же порядке возвращаются, как значения. Например,

(defun polar (x y)
  (values (sqrt (+ (* x x) (* y y))) (atan y x)))

(multiple-value-bind (r theta) (polar 3.0 4.0)
  (vector r theta))
    #(5.0 0.9272952)

Выражение (values) возвращает ноль значений. Это стандартная идиома для возврата из функции нулевого количества значений.

Иногда необходимо указать явно, что функция будет возвращать только одно значение. Например, функция

(defun foo (x y)
  (floor (+ x y) y))

будет возвращать два значения, потому что floor возвращает два значения. Может быть второе значение не имеет смысла в данном контексте, или есть причины не тратить время на вычисления второго значения. Функция values является стандартной идиомой для указания того, что будет возвращено только одно значение, как показано в следующем примере.

(defun foo (x y)
  (values (floor (+ x y) y)))

Это работает, потому что values возвращает только одно значения для каждой формы аргумента. Если форма аргумента возвращает несколько значений, то используется только первое, а остальные игнорируются.

В Common Lisp’е для вызывающего кода нет возможности различить, было ли возвращено просто одно значение или было возвращено только одно значение в с помощью values. Например значения, возвращённые выражением (+ 1 2) и (values (+ 1 2)), идентичны во всех отношениях: они оба просто равны 3.


[Константа] multiple-values-limit

Значение multiple-values-limit является положительным целым числом, которое невключительно указывает наибольшее возможное количество возвращаемых значений. Это значение зависит от реализации, но не может быть менее 20. (Разработчики рекомендуется делать это ограничение как можно большим без потери в производительности.) Смотрите lambda-parameters-limit и call-arguments-limit.


[Функция] values-list list

Все элементы списка list будут возвращены как несколько значений. Например:

(values-list (list a b c))  (values a b c)

Можно обозначить так,

(values-list list)  (apply #’values list)

но values-list может быть более ясным или эффективным.


[Макрос] multiple-value-list form

multiple-value-list вычисляет форму form и возвращает список из того количества значений, которое было возвращено формой. Например,

(multiple-value-list (floor -3 4))  (-1 1)

Таким образом, multiple-value-list и values-list являются антиподами. FIXME


[Специальный оператор] multiple-value-call function {form}*

multiple-value-call сначала вычисляет function для получения функции и затем вычисляет все формы forms. Все значения форм forms собираются вместе (все, а не только первые) и передаются как аргументы функции. Результат multiple-value-call является тем, что вернула функция. Например:

(+ (floor 5 3) (floor 19 4))
    (+ 1 4)  5
(multiple-value-call #’+ (floor 5 3) (floor 19 4))
    (+ 1 2 4 3)  10
(multiple-value-list form)  (multiple-value-call #’list form)


[Специальный оператор] multiple-value-prog1 form {form}*

multiple-value-prog1 вычисляет первую форму form и сохраняет все значения, возвращённые данной формой. Затем она слева направо вычисляет оставшиеся формы, игнорируя их значения. Значения, возвращённые первой формой, становятся результатом всей формы multiple-value-prog1. Смотрите prog1, которая всегда возвращает одно значение.


[Макрос] multiple-value-bind ({var}*) values-form{declaration}* {form}*

Вычисляется values-form и каждое значение результата связывается соответственно с переменными указанными в первом параметре. Если переменных больше, чем возвращаемых значений, для оставшихся переменных используется значение nil. Если значений больше, чем переменных, лишние значения игнорируются. Переменные связываются со значениями только на время выполнения форм тела, которое является неявным progn. Например:

(multiple-value-bind (x) (floor 5 3) (list x))  (1)
(multiple-value-bind (x y) (floor 5 3) (list x y))  (1 2)
(multiple-value-bind (x y z) (floor 5 3) (list x y z))
    (1 2 nil)


[Макрос] multiple-value-setq variables form

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

multiple-value-setq всегда возвращает одно значение, которое является первым из возвращённых значений формы form, или nil, если форма form вернула ноль значений.


[Макрос] nth-value n form

Формы аргументов n и form вычисляются. Значение n должно быть неотрицательным целым, и форма form должна возвращать любое количество значение. Целое число n используется, как индекс (начиная с нуля), для доступа к списку значений. Форма возвращает элемент в позиции n из списка результатов вычисления формы form. Если позиции не существует, возвращается nil.

В качестве примера, mod мог бы быть определён так:

(defun mod (number divisor)
  (nth-value 1 (floor number divisor)))


7.10.2 Правила управления возвратом нескольких значений

Часто случается так, что значение оператора или макроса определено как значение одного из подвыражений. Например, значение cond является значением последнего подвыражения в исполняемой ветке.

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

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

prog1, prog2, setq и multiple-value-setq — это формы, которые никогда не передают обратно несколько значений. Стандартный метод для явного указания возврата одного значения из формы x является запись (values x).

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

Например, если вы записали (cons (floor x)), тогда cons всегда получить только один аргумент (что, конечно, является ошибкой), хотя и floor возвращает два значения. Для того, чтобы поместить оба значения floor в cons, можно записать что-то вроде этого: (multiple-value-call #’cons (floor x)). В обычном вызове функции, каждый форма аргумента предоставляется только один аргумент. Если такая форма возвращает ноль значение, в качестве аргумента используется nil. А если возвращает более одного значения, все они, кроме первого, игнорируются. Также и условные конструкции, например if, которые проверяют значения формы, используют только одно первое значение, остальные игнорируются. Такие конструкции будут использовать nil если форма вернула ноль значений.