Clojure 是否可以对Lisp族语言实现自动套用?
也就是说,当您调用一个只有一个参数且具有>1 arity的函数时,它应该使用该参数并返回具有减少的arity的结果函数,而不是显示错误。这可以使用Lisp的宏来实现吗?在Scheme中,可以使用Clojure 是否可以对Lisp族语言实现自动套用?,clojure,lisp,scheme,common-lisp,currying,Clojure,Lisp,Scheme,Common Lisp,Currying,也就是说,当您调用一个只有一个参数且具有>1 arity的函数时,它应该使用该参数并返回具有减少的arity的结果函数,而不是显示错误。这可以使用Lisp的宏来实现吗?在Scheme中,可以使用curry过程来实现函数: (define (add x y) (+ x y)) (add 1 2) ; non-curried procedure call (curry add) ; curried procedure, expects two argumen
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]返回一个过程,该过程是过程的curry版本。当第一次应用结果过程时,除非为其提供了可接受的最大参数数,否则结果是接受附加参数的过程
您可以轻松实现一个宏,该宏在定义新过程时自动使用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
(定义货币(添加a和b)
(+a b))
添加
> #
(加1)
> #
((加1)2)
> 3
(加1至2)
> 3
Lisp已经有了功能性的Currying:
* (defun adder (n)
(lambda (x) (+ x n)))
ADDER
下面是我读到的有关Lisp宏的内容:
在纯Lisp中实现这一点是可能的。也可以使用宏来实现它,但是宏似乎会使非常基本的东西更加混乱。简短的回答是肯定的,尽管不容易
您可以将其实现为一个宏,将每个调用包装在
partial
中,尽管只在有限的上下文中。Clojure的一些特性会使这变得相当困难,例如变量arity函数和dynamit调用。Clojure缺乏一个正式的类型系统来具体决定调用何时可以没有更多参数,何时应该被调用。这是可能的,但如果您想要一个有用的结果,这并不容易
- 如果您想要一种总是进行简单咖喱的语言,那么实现起来很容易。您只需将具有多个输入的每个应用程序转换为嵌套应用程序,对于具有多个参数的函数也是如此。有了语言设施,这是一个非常简单的练习。(在其他Lisp中,您可以通过使用代码周围的宏获得类似效果。) (顺便说一句,我有一种语言就可以做到这一点。它具有自动咖喱语言的全部可爱性,但并不实用。) 但是,它并不太有用,因为它只适用于一个参数的函数。您可以通过一些黑客攻击使其变得有用,例如,将围绕您的语言的lisp系统的其余部分视为一门外语,并提供使用它的表单。另一种选择是为您的语言提供有关周围lisp函数的算术信息。这两项都需要更多的工作
- 另一种选择是只检查每个应用程序。换言之,你每天都要转身
检查(f x y z)
的算术性并在参数不足时创建闭包的代码。这本身并不难,但它将导致一个巨大的间接费用价格。您可以尝试使用一种类似的技巧,即在宏级别使用函数的算术信息,以了解应该在何处创建此类闭包——但本质上也是一样的f
(+1 2)3)
?这里似乎有一个简单的答案,但是这个呢?(翻译成您最喜欢的lisp方言)
在这种情况下,您可以通过多种方式拆分(foo 1 2 3)
。另一个问题是,您如何处理以下内容:
(list +)
这里有+
作为一个表达式,但您可以确定这与将其应用于符合+
算术的零输入相同,但是如何编写计算为加法函数的表达式呢?(旁注:ML和Haskell通过不使用空函数来“解决”这个问题…)
其中一些问题可以通过确定每个“真正的”应用程序都必须有paren来解决,因此,
+
本身永远不会被应用。但是,这失去了使用自动咖喱语言的许多可爱之处,而且您仍然需要解决一些问题…正如Alex W所指出的,这是一本通用Lisp的通用Lisp烹饪书,其中包含了一个用于通用Lisp的“咖喱”函数。具体示例在该页的下方:
(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))))
自动咖喱应该不会那么难实现,所以我尝试了一下。请注意,以下内容没有经过广泛测试,也没有检查是否没有太多的arg(当存在该数量或更多arg时,函数才完成):
不过,这似乎有效:
* (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
虽然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}>
*(自动换币自动换币-+(x y z)
(+xyz))
自动咖喱-+
*(funcall(自动咖喱-+1)2 3)
6.
*(自动咖喱-+1)
#
当然,您只需确定语言的确切语义,然后实现您自己的加载程序,该加载程序将把源文件转换为实现语言
例如,您可以将每个用户函数调用(fabc…z)
转换为(((fa)b)c)…z)
,并将每个(定义(fabc…z)
转换为(定义f(lambda)(lambda)(lambda(b)(lambda)(lambda)(c)(…(lambda(z)…))))
,在一个方案之上,创建一个自动兑换方案(这当然会禁止varargs函数)
您还需要定义自己的原语,将varargs函数(如(+)
)转换为二进制,并将它们的应用程序转换为使用(+1234)
==>(fold(+)(list 1234)0)
或其他东西,或者可能只是在新语言中调用(+1234)
非法
* (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
(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))))
* (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}>