也就是说,当您调用一个只有一个参数且数量大于 1 的函数时,它应该不显示错误,而是对该参数进行 curry 并返回具有减少的数量的结果函数。使用 Lisp 的宏可以做到这一点吗?
6 回答
如果你想要一个有用的结果,这是可能的,但并不容易。
如果您想要一种总是进行简单柯里化的语言,那么实现很容易。您只需将多个输入的每个应用程序转换为嵌套应用程序,对于多个参数的函数也是如此。借助Racket 的语言功能,这是一个非常简单的练习。(在其他 lisps 中,您可以通过围绕要使用它的代码的一些宏来获得类似的效果。)
(顺便说一句,我在 Racket 之上有一种语言可以做到这一点。它具有自动柯里化语言的全部可爱之处,但它并不实用。)
但是,它并不太有用,因为它仅适用于一个参数的函数。您可以通过一些黑客攻击使其变得有用,例如,将围绕您的语言的 lisp 系统的其余部分视为外语,并提供使用它的表单。另一种选择是为您的语言提供有关周围 lisp 函数的数量信息。其中任何一个都需要更多的工作。
另一种选择是只检查每个应用程序。换句话说,你转动每一个
(f x y z)
到检查 arity 的代码中,
f
如果没有足够的参数,将创建一个闭包。这本身并不太难,但会导致显着的间接费用。您可以尝试使用类似的技巧,了解您将在宏级别使用的函数的一些信息,以了解应该在哪里创建此类闭包——但这基本上以相同的方式困难。
但是有一个更严重的问题,在你想做的事情的高层次上。问题是可变参数函数不能很好地与自动柯里化配合使用。例如,采用如下表达式:
(+ 1 2 3)
您将如何决定是否应该按原样调用它,或者是否应该将其翻译为((+ 1 2) 3)
?似乎这里有一个简单的答案,但是这个呢?(翻译成你最喜欢的 lisp 方言)
(define foo (lambda xs (lambda ys (list xs ys))))
在这种情况下,您可以通过多种方式拆分 a (foo 1 2 3)
。另一个问题是您如何处理以下内容:
(list +)
在这里你有+
一个表达式,但你可以决定这与将它应用于适合+
sarity 的零输入相同,但是你如何编写一个计算加法函数的表达式?(旁注:ML 和 Haskell 通过没有无效函数来“解决”这个问题......)
这些问题中的一些可以通过决定每个“真实”应用程序必须具有它的括号来解决,因此 a+
本身永远不会被应用。但这失去了自动咖喱语言的可爱之处,你仍然有问题要解决......
在 Scheme 中,可以使用以下curry
过程对函数进行 curry:
(define (add x y)
(+ x y))
(add 1 2) ; non-curried procedure call
(curry add) ; curried procedure, expects two arguments
((curry add) 1) ; curried procedure, expects one argument
(((curry add) 1) 2) ; curried procedure call
从球拍的文档:
[curry] 返回一个过程,它是 proc 的柯里化版本。当第一次应用结果过程时,除非给定它可以接受的最大参数数量,否则结果是一个接受附加参数的过程。
curry
您可以轻松实现定义新过程时自动使用的宏,如下所示:
(define-syntax define-curried
(syntax-rules ()
((_ (f . a) body ...)
(define f (curry (lambda a (begin body ...)))))))
add
现在将柯里化以下定义:
(define-curried (add a b)
(+ a b))
add
> #<procedure:curried>
(add 1)
> #<procedure:curried>
((add 1) 2)
> 3
(add 1 2)
> 3
简短的回答是肯定的,但并不容易。
您可以将其实现为将每个调用都包含在 中的宏partial
,尽管仅在有限的上下文中。Clojure 有一些特性会使这变得相当困难,例如可变参数函数和动态调用。Clojure 缺乏正式的类型系统来具体决定调用何时不能有更多参数并且应该实际调用。
Lisp 已经有了函数式柯里化:
* (defun adder (n)
(lambda (x) (+ x n)))
ADDER
http://cl-cookbook.sourceforge.net/functions.html
这是我读到的关于 Lisp 宏的内容:https ://web.archive.org/web/20060109115926/http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html
可以在纯 Lisp 中实现这一点。也可以使用宏来实现它,但是似乎宏会使非常基本的东西变得更加混乱。
正如 Alex W 所指出的,Common Lisp Cookbook确实给出了Common Lisp的“curry”函数的示例。具体示例在该页面的下方:
(declaim (ftype (function (function &rest t) function) curry)
(inline curry)) ;; optional
(defun curry (function &rest args)
(lambda (&rest more-args)
(apply function (append args more-args))))
自动柯里化应该不难实现,所以我尝试了一下。请注意,以下内容没有经过广泛测试,也没有检查是否有太多 args(当有该数量或更多时,该函数才完成):
(defun auto-curry (function num-args)
(lambda (&rest args)
(if (>= (length args) num-args)
(apply function args)
(auto-curry (apply (curry #'curry function) args)
(- num-args (length args))))))
似乎工作,但:
* (auto-curry #'+ 3)
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F78EB9}>
* (funcall (auto-curry #'+ 3) 1)
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F7A689}>
* (funcall (funcall (funcall (auto-curry #'+ 3) 1) 2) 5)
8
* (funcall (funcall (auto-curry #'+ 3) 3 4) 7)
14
上面的一些宏语法糖的原始版本(不能正确处理完整的 lambda 列表,只是简单的参数列表):
(defmacro defun-auto-curry (fn-name (&rest args) &body body)
(let ((currying-args (gensym)))
`(defun ,fn-name (&rest ,currying-args)
(apply (auto-curry (lambda (,@args) ,@body)
,(length args))
,currying-args))))
似乎有效,尽管对 funcall 的需求仍然很烦人:
* (defun-auto-curry auto-curry-+ (x y z)
(+ x y z))
AUTO-CURRY-+
* (funcall (auto-curry-+ 1) 2 3)
6
* (auto-curry-+ 1)
#<CLOSURE (LAMBDA (&REST ARGS)) {1002B0DE29}>
当然,你只需要为你的语言确定准确的语义,然后实现你自己的加载器,它将你的源文件翻译成实现语言。
例如,您可以将每个用户函数调用(f a b c ... z)
转换为(...(((f a) b) c)... z)
,并将每个(define (f a b c ... z) ...)
转换(define f (lambda(a) (lambda(b) (lambda(c) (... (lambda(z) ...) ...)))))
为 Scheme 的顶部,以拥有一个自动柯里化 Scheme(这当然会禁止 varargs 函数)。
您还需要定义自己的原语,将 varargs 函数(如 eg (+)
)转换为二进制,并将其应用程序转换为使用fold
eg (+ 1 2 3 4)
==>(fold (+) (list 1 2 3 4) 0)
或其他东西 - 或者可能只是在您的新语言(+ 1 2 3 4)
中将此类调用视为非法,期望其用户自己写表格。fold
这就是我所说的“为你的语言决定……语义”的意思。
加载器可以像将文件内容包装到对宏的调用一样简单——然后根据您的问题,您必须实现该宏。