如何编写一个用其参数调用函数的函数?

时间:2016-06-17 07:22:47

标签: common-lisp

我正在尝试编写包含其他函数的函数,但我不确定如何在维护合理的lambda列表的同时正确传递参数。

E.g。如果我有一个功能

(defun f (x &key y z) ...)

我想写点像

(defun g (x &key y z)
  (h (f x :y y :z z)))

这是不能令人满意的,因为我想从f调用g并调用g的确切参数,但这不会发生(例如,我不想提供f的关键字参数,这些参数未被调用者提供给g

我最初写的是:

(defun g (&rest f-args)
  (apply #'f f-args))

这就是我想要的效果,但是g的lambda列表现在非常神秘,我不得不导航到f以查看参数应该是什么。

我确实想出了一个解决方案(而且它大部分都令人满意,所以我将其作为答案发布),但我需要明确每一个关键参数,以及大型lambda列表(例如,如果我想要包装{ {3}}),这将是一种痛苦。我希望也许有更好的方法。

2 个答案:

答案 0 :(得分:3)

您可以通过从另一个函数复制lambda列表来编写一个定义函数的宏。问题在于,没有标准方法来获取lambda列表,但对于SBCL,您可以使用SB-INTROSPECT:FUNCTION-LAMBDA-LIST(尽管它不能与(declaim (optimize (debug 0)))一起使用)。您可以尝试阅读Swank源代码,了解它如何获得各种实现的lambda列表。

(defmacro define-wrapper (name lambda-source &body body)
  `(defun ,name ,(sb-introspect:function-lambda-list lambda-source)
     ,@body))

(defun f (x &key (y 3) (z 4))
  (+ x y z))

(define-wrapper g f
  (* 2 (f x :y y :z z)))

(f 2) ;=> 9
(g 2) ;=> 18

由于代码没有显示变量定义,因此有点难看。更复杂的解决方案可能是做

之类的事情
;; Requires Alexandria.
(defmacro define-wrapper (name lambda-source &body body)
  (let ((lambda-list (sb-introspect:function-lambda-list lambda-source)))
    (multiple-value-bind (required optional rest keywords)
        (alexandria:parse-ordinary-lambda-list lambda-list)
      (declare (ignore rest))
      `(defun ,name ,lambda-list
         ,@(sublis `((_ . (,lambda-source ,@(loop for r in required collect r)
                                          ,@(loop for (name init suppliedp)
                                                  in optional collect name)
                                          ,@(loop for ((k-name name) init suppliedp)
                                                  in keywords
                                                append (list k-name name)))))
                   body)))))

(defun f (x &key (y 3) (z 4))
  (+ x y z))

(define-wrapper g f
  (* 2 _))

使用给定参数调用函数_替换包装器中的F。您仍然需要记住参数变量存在并且可能与您自己定义的变量冲突。

将所有参数传递给函数,无论它们是否被赋予。这可能会破坏一个行为不同的函数,具体取决于是否提供了参数。您可以使用APPLY来避免这种情况,但它有点复杂。

(defmacro define-wrapper (name lambda-source &body body)
  (let ((lambda-list (sb-introspect:function-lambda-list lambda-source)))
    (alexandria:with-gensyms (deparsed-arglist-sym
                              key-sym val-sym suppliedp-sym)
      (multiple-value-bind (required optional rest keywords)
          (alexandria:parse-ordinary-lambda-list lambda-list)
        (declare (ignore rest))
        (multiple-value-bind (body declarations docstring)
            (alexandria:parse-body body :documentation t)
          `(defun ,name ,lambda-list
             ,@(when docstring (list docstring))
             ,@declarations
             (let ((,deparsed-arglist-sym
                     (nconc (loop for ,val-sym in (list ,@required) collect ,val-sym)
                            (loop for (,val-sym . ,suppliedp-sym)
                                    in (list ,@(loop for (name init suppliedp)
                                                       in optional
                                                     collect (list 'cons name
                                                                   (or suppliedp t))))
                                  when ,suppliedp-sym collect ,val-sym)
                            (loop for (,key-sym ,val-sym ,suppliedp-sym)
                                    in (list ,@(loop for ((kname name) init suppliedp) 
                                                       in keywords
                                                     collect (list 'list kname name
                                                                   (or suppliedp t))))
                                  when ,suppliedp-sym append (list ,key-sym ,val-sym)))))
               ,@(sublis `((_ . (apply #',lambda-source ,deparsed-arglist-sym)))
                         body))))))))

(define-wrapper bar drakma:http-request
  "Return the length of a response to http-request."
  ;; HTTP-REQUEST has some &aux variables.
  (declare (ignore drakma::unparsed-uri
                   drakma::args))
  (length _))

(bar "http://www.google.com") ;=> 11400 (14 bits, #x2C88)

答案 1 :(得分:2)

我想出了这个:

(defun g (x &rest f-keys &key y z)
  (declare (ignorable y z)) 
  (apply #'f x f-keys))

对于小型lambda列表来说这很棒,但我希望我能做得更好 除非我明确地输入默认值,否则我也看不到默认值。