【问题标题】:Function with rest arguments calling a function with rest arguments带剩余参数的函数调用带剩余参数的函数
【发布时间】:2018-10-13 21:52:07
【问题描述】:

假设我们有一个函数func1:

(defun func1 (&rest values)
  ; (do something with values...)
  (loop for i in values collect i))

现在,我们有一个函数func2,它调用func1:

(defun func2 (&rest values)
  ; (do something with values...)
  (func1 ???))

我应该用什么代替??? 来将func2 的values 的所有参数“复制”到func1 的values ?

例如,我会有以下行为:

(func2 1 2 3 4) ; result is (1 2 3 4) and not ((1 2 3 4)).

在an earlier question 我试图做这样的事情:

(defun func2 (&rest values)
  (macrolet ((my-macro (v)
               `(list ,@v)))
    (func1 (my-macro values))))

但 defun 无法获取值,因为它不是运行时。在this answer中,他建议我使用apply,但是这个函数也需要一个&rest参数,所以它并没有解决我的问题......

如果可能的话,我宁愿避免改变这两个函数的原型,以及func1的行为。

【问题讨论】:

  • 它有效,谢谢! :D 我很惭愧,我把funcall 和apply 联合起来,忘记了apply 拿了一个清单作为论据。解决了。

标签: common-lisp


【解决方案1】:

在普通的 lisp 中,它必须是

(apply #'func1 values) ;; since `func1` has to be looked up in function namespace

记住,Clojure 和 Racket/Scheme 是 Lisp1,common lisp 是 Lisp2。

替代解决方案(只是为了方便)

我在问自己,没有apply 怎么办——只是为了。 问题

`(func2 ,@values)

是,如果例如

 (func2 (list 1 2 3) (list 4) 5)

被调用,values 变量是((1 2 3) (4) 5) 但是当它拼接成(func1 ,@values)时,创建的是 (func1 (1 2 3) (4) 5)。但如果我们将其与func2 通话进行比较, 它应该是(func1 (list 1 2 3) (list 4) 5) 这可能是不可能的,因为当(func2 (list 1 2 3) (list 4) 5) 被调用时- 以 lisp 方式 - func2 的参数在进入 func2 的函数体之前都被评估,所以我们最终以 values 作为已评估参数的列表,即((1 2 3) (4) 5)。

所以不知何故,关于最后一个表达式中func1 的参数,我们是一个评估步骤的另类。

但是有一个quote解决方案,我们设法在最后一个表达式中将每个参数提供给func1之前引用每个参数,以“同步” func1 函数调用 - 让参数的评估暂停一轮。

所以我的第一个目标是在 func2 正文中生成一个新的 values 列表,其中每个值列表的参数都被引用(这是在 let 绑定中完成的)。 然后在最后将这个quoted-values列表拼接成最后一个表达式:(func1 '(1 2 3) '(4) '5),对于这种问题/对于这种调用,可以认为相当于(func1 (list 1 2 3) (list 4) 5)。 这是通过以下代码实现的:

(defun func2 (&rest vals)
  (let ((quoted-values (loop for x in vals
                                     collect `',x)))
    ; do sth with vals here - the func2 function -
    (eval `(func1 ,@quoted-values))))

这是一种宏(顺便说一句,它创建代码。它组织新代码)但在运行时执行和创建 - 而不是在预编译时。我们使用eval 即时执行生成的代码。

和macroexpand-1 一样,我们可以通过删除eval 来查看func1 表达式“扩展”到的结果——代码——我称之为func2-1:

(defun func2-1 (&rest vals)
  (let ((quoted-values (loop for x in vals
                                     collect `',x)))
    ; do sth with vals here - the func2 function -
    `(func1 ,@quoted-values)))

如果我们运行它,它会在 func2 版本中立即将最后一个表达式作为代码返回:

(func2-1 (list 1 2 3) (list 4) 5)
;; (FUNC1 '(1 2 3) '(4) '5) ;; the returned code
;; the quoted arguments - like desired!

如果我们使用func2 调用它,就会发生这种情况(因此评估func1 all:

(func2 (list 1 2 3) (list 4) 5) 
;; ((1 2 3) (4) 5)  ;; the result of (FUNC1 '(1 2 3) '(4) '5)

所以我想说这正是你想要的!

【讨论】:

    【解决方案2】:

    列表与散布参数

    在 Common Lisp 中,将列表作为列表而不是作为 spread 参数传递是一种很好的风格:

    (foo (list 1 2 3))   ; better interface
    
    (foo 1 2 3)          ; interface is not so good
    

    该语言已被定义为编译器可以使用高效的函数调用,这意味着可以传递给函数的参数数量是有限的。有一个标准变量可以告诉我们特定实现支持多少个参数:

    这是我 Mac 上的 LispWorks:

    CL-USER 13 > call-arguments-limit
    2047
    

    一些实现允许更多数量的参数。但是这个数字可以低至 50 - 例如 ABCL,JVM 上的 Common Lisp,只允许 50 个参数。

    使用参数列表计算

    但有时我们希望将参数作为一个列表,然后我们可以使用&rest 参数:

    (lambda (&rest args)
      (print args))
    

    这有点效率低下,因为参数将包含一个列表。通常 Lisp 会尽量避免使用 cons 列表作为参数——如果可能的话,它们将在寄存器或堆栈中传递。

    如果我们知道参数列表不会被使用,那么我们可以给编译器一个使用堆栈分配的提示——如果可能的话:

    (lambda (&rest args)
      (declare (dynamic-extent args))
      (reduce #'+ args))
    

    在上面的函数中,参数列表可以在离开函数时释放 - 因为参数列表不再使用。

    如果您想将这些参数传递给另一个函数,您可以使用FUNCALL,通常更有用的是APPLY:

    (lambda (&rest args)
      (funcall #'write (first args) (second args) (third args)))
    

    或更有用的:

    (lambda (&rest args)
      (apply #'write args))
    

    还可以在要应用的列表之前向APPLY 添加其他参数:

    CL-USER 19 > ((lambda (&rest args)
                    (apply #'write
                           (first args)               ; the object
                           :case :downcase            ; additional args 
                           (rest args))
                    (values))
                  '(defun foo () 'bar)
                  :pretty t
                  :right-margin 15)
    (defun foo ()
      'bar)
    

    【讨论】:

      猜你喜欢
      • 2020-06-09
      • 1970-01-01
      • 2021-11-15
      • 1970-01-01
      • 1970-01-01
      • 2021-10-02
      • 1970-01-01
      • 2022-01-03
      • 1970-01-01
      相关资源
      最近更新 更多