web-dev-qa-db-ja.com

Common Lispでの&restと&keyの同時使用

&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の両方を使用できますか?

5
orematasaburo

これは、自分がやりたいことを行う方法の例です。これはかなり単純ですが、0個以上のキーワード引数とともに、任意の数の引数を取る関数を定義できます。次に、引数からキーワードとその値を引き出し、適切に関数を呼び出す小さなトランポリンがあります。

これは本番品質のコードを意味するものではありません。トランポリン作成機能に、たとえば「任意のキーワード」だけでなく、知りたいキーワードを正確に認識させる方が明らかに良いでしょう。

(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/kwdefunと同じことをしない可能性があります。特に、関数を適切に定義する(コンパイル時に定義しない)には十分ですが、コンパイル時に関数が存在することをコンパイラが認識しない場合があります時間(警告が表示される可能性があります)、実装固有のマジックも実行されません。

1
tfb