Использование & rest параметров в Common Lisp - PullRequest
0 голосов
/ 20 февраля 2019

У меня возникли проблемы с использованием и параметров покоя в программе lisp (с Common Lisp).

Мои знания о синтаксисе не идеальны, возможно, я что-то делаю не так.

У меня есть две функции:

(defun func-one(&rest params) .....)

(defun func-two(param-a param-b &rest params) .....)

Они работают, но в какой-то момент внутри func-one мне нужно вызвать функцию-две, например:

(func-two value-a value-b params)

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

*** _ =: (1 2 3 4 5) is not a number

Поскольку "& rest params" предназначено для хранения списканомера, я понимаю сообщение.Но как я могу получить желаемый результат?

(1 2 3 4 5) passed in the "&rest params" should be seen as : 1 2 3 4 5 by func-two
and not as one element which is a list (indeed not a number).

Итак, как правильно назвать func-two?Вместо того, что я сделал.

1 Ответ

0 голосов
/ 20 февраля 2019

В этом ответе я начну с объяснения проблемы и затем покажу два разных подхода к ее решению.

Понимание проблемы

Начнем с рассмотрения двух определений:

(defun func-one (&rest params) ...)

Итак, func-one - это функция, которая принимает любое количество аргументов (включая ноль).Внутри его тела все аргументы будут заключены в список и будут связаны с params: длина params будет количеством аргументов, предоставленных функции.

(defun func-two (param-a param-b &rest params) ...)

func-twoпринимает как минимум два аргумента и столько, сколько вам нужно (до предела реализации: это верно и для func-one).Первые два аргумента будут связаны с param-a и param-b, а все остальные аргументы будут заключены в список и связаны с params.

Так что мыМожно написать небольшую фальшивую версию func-two, которая объясняет, какие аргументы он получает:

(defun func-two (param-1 param-2 &rest params)
  (format t "~&~D arguments~%param-1: ~S~%param-2: ~S~%params:  ~S~%"
          (+ 2 (length params))
          param-1 param-2 params)
  (values))

И теперь мы можем назвать ее различными способами:

> (func-two 1 2)
2 arguments
param-1: 1
param-2: 2
params:  nil

> (func-two 1 2 3)
3 arguments
param-1: 1
param-2: 2
params:  (3)

> (func-two 1 2 3 4)
4 arguments
param-1: 1
param-2: 2
params:  (3 4)

> (func-two 1 2 '(3 4))
3 arguments
param-1: 1
param-2: 2
params:  ((3 4))

И вот проблема:если у вас уже есть куча вещей, упакованных в список, то когда вы вызываете func-two, этот список является только одним аргументом функции: они не 'всеостальные аргументы - это то, что вам нужно.

Существует два подхода к решению этой проблемы, описанные в следующих разделах.

Первый подход: используйте apply

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

> (apply #'func-two 1 2 '(3 4))
4 arguments
param-1: 1
param-2: 2
params:  (3 4)

> (apply #'func-two '(1 2 3 4))
4 arguments
param-1: 1
param-2: 2
params:  (3 4)

Вы можете видеть, что делает apply: первым аргументом для него является вызываемая функция, аргументом last для него является «все остальные аргументы», и любые аргументы между ними являются некоторыми главными аргументами.

[Остальная часть этого ответа говорит о дизайне, который обязательно является вопросом мнения.]

Использование apply - вполне приличный технический ответ (есть некоторые проблемы с длиной списка аргументов ипроизводительность, но я думаю, что они вторичны здесь).Однако очень часто случается так, что необходимость использовать apply таким образом является признаком некоторой проблемы проектирования.Чтобы понять, почему это так, подумайте, что ваши определения функций подразумевают для читателя-человека.

(defun frobnicate (a b &rest things-to-process) ...)

Это означает, что функция frobnicate действительно имеет три аргумента в своем теле (вызов ее связывает три переменные),где третий аргумент - это некоторый список вещей для обработки.Но на уровне user я хочу вызвать его без необходимости явно указывать этот список, поэтому я указываю этот третий аргумент как &rest, что означает, что функция фактически принимает два или более аргумента с точки зрения вызывающего.

Но затем рассмотрим этот фрагмент кода:

(apply #'frobnicate this that things-to-process)

Что это значит для того, кто читает его?Что ж, это означает, что вы точно знаете, какую функцию вы хотите вызвать, frobnicate, и у вас уже есть три аргумента, которые frobnicate действительно хочет: какими бы ни были первые два, а затем списоквещи для обработки.Но из-за способа определения frobnicate вы не можете просто передать их ему: вместо этого вы должны распространить things-to-process на набор отдельных аргументов с помощью apply , а затем немедленно развернуть его обратно в (новый) список в процессе вызова frobnicate.Это плохо пахнет.

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

Второй подход: изменить конструкцию

Таким образом, второй ответ на проблему заключается в изменениидизайн, так что это импеданс-несоответствие не происходит.Способ устранения несоответствия импеданса, подобный этому, заключается в определении вещей в слоях:

(defun frobnicate (a b &rest things-to-process)
  ;; user convenience function
  (frob a b things-to-process))

(defun frob (a b things-to-process)
  ;; implementation
  ...)

И теперь, в реализации, где у вас уже есть список объектов для обработки, вы вызываете frob вместо frobnicate, и вам больше не нужно использовать apply.

...