Ошибка лямбда-списка с аргументами & rest и & key в Common Lisp - PullRequest
1 голос
/ 13 марта 2019

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

(defun create-symbol (&rest objects &key intern (package *package*))
  "Creates a symbol from the objects."
  (let ((arg-string (format nil "~{~A~^~}" (first objects))))
    (if intern
        (values (intern arg-string package))
      (make-symbol arg-string))))

Например, (create-symbol "A" 1) производит Unknown &KEY argument: "A" вместо #:A1.

Также не уверен, является ли (first objects) правильным способом доступа к аргументам & rest, если ключевые слова не переданы.

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

Редактировать: Учитывая комментарии ниже, похоже, что ручной синтаксический анализ аргументов может быть одним из способов, когда они являются комбинациями ключевых слов лямбда-списка и необязательными, & rest, и & key. Следующая функция, кажется, делает то, что я изначально намеревался:

(defun create-symbol (&rest objects&keys)
  "Creates a symbol from the objects,
   with optional keywords :intern and :package."
  (let* ((keys (member-if #'keywordp objects&keys))
         (objects (ldiff objects&keys keys))
         (arg-string (format nil "~{~A~^~}" objects)))
    (if (getf keys :intern)
      (intern arg-string (or (getf keys :package) *package*))
      (make-symbol arg-string))))

Ответы [ 2 ]

1 голос
/ 13 марта 2019

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

  1. Функция имеет три вида аргументов для анализа: обязательный, необязательный и остальные
  2. Любые аргументы, которые появляются перед ключевым словом лямбда-списка (например, &optional), являются обязательными аргументами. Они должны быть переданы. Для дальнейших шагов подсчитываются только аргументы, переданные после обязательных аргументов.
  3. После обязательных аргументов в лямбда-списке может прийти &optional и затем необязательные аргументы. Если есть какие-либо переданные аргументы, которые еще предстоит проанализировать, они принимаются как необязательные аргументы, пока не останется необязательных аргументов для анализа. Если не осталось аргументов для разбора, то все готово.
  4. После необязательных аргументов (если они есть) может быть аргумент rest (&rest, за которым следует символ для привязки), аргументы с ключевыми словами (с префиксом &key с ключевым словом &allow-other-keys, изменяющим синтаксический анализ. Любые переданные аргументы. еще не проанализированные на этом этапе называются аргументами отдыха. Вот как они анализируются:
    1. Если в лямбда-списке был аргумент &rest, свяжите его с любыми аргументами, которые не были проанализированы.
    2. Если есть какие-либо аргументы ключевого слова, тогда требуется, чтобы число остальных аргументов было четным, и для каждого оставленного аргумента сначала прочитайте ключ и сопоставьте его с символом несвязанного аргумента ключевого слова. Свяжите этот аргумент со следующим аргументом отдыха. Если клавиша повторяется, это ошибка. Если ключ неизвестен, это ошибка, если не указано ключевое слово &allow-other-keys.

Можно представить следующее преобразование:

(defun f ( { args } [ &rest rest ] &key { kwargs } [ &allow-other-keys ] )
  ...)

;; - - ->

(defun f ( { args } &rest rest )
  (destructuring-bind ( &key { kwargs } [ &allow-other-keys ] ) rest
    ...))

Это может сделать это немного более ясным.


Вы можете заставить свою функцию вести себя так, как вы хотите (не используя getf, хотя из-за четности), но я бы сказал, что это неправильно. Учтите следующее:

(defun foobar-sym (k)
  (create-symbol 'foo k 'bar))

CL-USER> (foobar-sym :baz)
#:FOOBAZBAR
CL-USER> (foobar-sym :intern)
FOO

это немного странно.

0 голосов
/ 13 марта 2019

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

(defun test-key-rest (&rest args &key a (b t))
    (list 'args args 'a a 'b b))
(test-key-rest :a 1 :b 2); => (ARGS (:A 1 :B 2) A 1 B 2)
(test-key-rest :a 1 :b 2 "rest now?");;; Error: The passed key "rest now?" is not defined for this function
(test-key-rest :a 1 :b 2 :c 3);;; Error: The passed key :C is not defined for this function

Можно, возможно, использовать & allow-other-keys , но я думаю, что это будет грязно.Я вспомнил, как читал об этой ситуации в Practical Common Lisp , где Питер Сейбел пишет (выделение мое):

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

Я предлагаю разделить два списка аргументов. destructuring-bind упрощает:

(defun test-two-arg-lists (keys &rest args)
    (destructuring-bind (&key (a nil) (b t)) keys
        (list 'a a 'b b 'args args)))
(test-two-arg-lists (list :a 1) "more" "please"); => (A 1 B T ARGS ("more" "please"))

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

(defmacro test-two-nice (keys &rest args)
    `(test-two-arg-lists (list ,@keys) ,@args))
(test-two-nice (:a 1) "more" "please"); => (A 1 B T ARGS ("more" "please"))

Итак, чтобы собрать все вместе:

(defun create-symbol-fn (keys &rest objects)
  "Creates a symbol from the objects."
  (destructuring-bind (&key intern (package *package*)) keys
    (let ((arg-string (format nil "~{~A~}" objects)))
      (if intern
          (values (intern arg-string package))
        (make-symbol arg-string)))))
(defmacro create-symbol (keys &rest objects)
  `(create-symbol-fn (list ,@keys) ,@objects))
(create-symbol (:intern nil) 'a 'b 'c 'd); => #:ABCD
...