использование & rest и & key одновременно в Common Lisp - PullRequest
3 голосов
/ 22 июня 2019

Я хочу использовать одновременно и &rest, и &key.Однако попытка кода ниже:

(defun test (&rest args &key (name "who")) nil)
(test 1 2 3 4 5 :name "hoge")

вызывает ошибку:

*** - TEST: аргументы ключевого слова в (1 2 3 4 5: NAME "hoge")должно происходить попарно

, и когда я даю только параметр ключевого слова, такой как (test :name "hoge"), он работает.Можно ли использовать как & rest, так и & key?

Ответы [ 3 ]

3 голосов
/ 22 июня 2019

Как правило, не рекомендуется смешивать параметры отдыха с параметрами ключевых слов в определении функции в Common Lisp. Если вы это сделаете, вам, вероятно, следует подумать о переписывании определения функции, поскольку это может привести к неожиданному поведению. Если оба параметра & rest и & key появляются в списке параметров, то происходят оба события - все оставшиеся значения, включая сами ключевые слова, собираются в список, связанный с параметром & rest, а соответствующие значения также привязываются к ключу & параметры. Таким образом, параметр ключевого слова (name "who") по умолчанию привязан к вашему списку остальных параметров. если вы попытаетесь ввести аргументы (1 2 3 4 5), вы получите ошибку, потому что они не привязаны к вашему параметру (имя «кто»). Вот пример:

(defun test (&rest args &key (name "who"))
   (list args name))

Здесь у нас есть определение вашей функции. Если мы попытаемся вызвать функцию, которая возвращает список аргументов, мы увидим, что параметры & rest связаны с параметрами они & key здесь:

CL-USER> (test :name "Davis")
((:NAME "Davis") "Davis")

Смешивая параметры & rest и параметры ключевого слова в одном и том же списке параметров, вы не сможете ввести остальные параметры, которые не соответствуют параметру вашего ключевого слова, поэтому вы вводите здесь точку прерывания.

Теперь, если вы хотите создать макрос, вы можете технически использовать несколько списков параметров в определении и добавлять параметры ключевых слов в один список, а параметры & rest (или & body) в другой список:

 (defmacro hack-test ((&key (name "who")) &body body)
   `(list ,name ,@body))

CL-USER> (hack-test (:name "Ricky")
                (+ 2 3))
("Ricky" 5)

CL-USER> (hack-test ()
                 (+ 2 4)
                 (+ 4 5)
                 (+ 9 9))
("who" 6 9 18)
CL-USER> 
1 голос
/ 24 июня 2019

Сочетание &key и &rest на самом деле очень часто встречается в Common Lisp, но почти всегда вместе с &allow-other-keys.

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

(defun my-write (object &rest args &key stream &allow-other-keys)
  (write "my wrapper" :stream stream)
  (apply #'write object args))

Вы найдете много мест, будь это &rest / &key / &allow-other-keys шаблоны используются там, где на самом деле CLOS реализован .

1 голос
/ 24 июня 2019

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

Это не означает, что это код производственного качества: было бы лучше иметь батут, производящий батут.Функция точно знает, какие ключевые слова она искала, например, которые могут быть известны, а не просто «любые ключевые слова».

(defun make-kw-trampoline (fn)
  ;; Given a function which takes a single rest arg and a bunch of
  ;; keyword args, return a function which will extract the keywords
  ;; from a big rest list and call it appropriately
  (lambda (&rest args)
    (loop for (arg . rest) on args
          if (keywordp arg)
          if (not (null rest))
          collect arg into kws and collect (first rest) into kws
          else do (error "Unpaired keyword ~S" arg)
          finally (return (apply fn args kws)))))

(defmacro defun/rest/kw (name (rest-arg and-key . kw-specs) &body decls-and-forms)
  ;; Define a function which can take any number of arguments and zero
  ;; or more keyword arguments.
  (unless (eql and-key '&key)
    (error "um"))
  (multiple-value-bind (decls forms) (loop for (thing . rest) on decls-and-forms
                                           while (and (consp thing)
                                                      (eql (first thing) 'declare))
                                           collect thing into decls
                                           finally (return
                                                    (values decls (cons thing rest))))
    `(progn
       (setf (fdefinition ',name)
             (make-kw-trampoline (lambda (,rest-arg &key ,@kw-specs)
                                   ,@decls
                                   (block ,name
                                     ,@forms))))
       ',name)))

Так что, если я сейчас определю функцию, подобную этой:

(defun/rest/kw foo (args &key (x 1 xp))
  (declare (optimize debug))
  (values args x xp))

Тогда я могу назвать это так:

> (foo 1 2 3)
(1 2 3)
1
t

> (foo 1 2 :x 4 3)
(1 2 :x 4 3)
4
t

Обратите внимание, что defun/rest/kw может не делать то же самое, что defun: в частности, я думаю, что этого достаточно для правильного определения функции (ине определять его во время компиляции), но компилятор может не осознавать, что функция существует во время компиляции (поэтому могут быть предупреждения), и он также не выполняет никакой специфической для реализации магии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...