Clojure 是否可以对Lisp族语言实现自动套用?

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

也就是说,当您调用一个只有一个参数且具有>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 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
    的算术性并在参数不足时创建闭包的代码。这本身并不难,但它将导致一个巨大的间接费用价格。您可以尝试使用一种类似的技巧,即在宏级别使用函数的算术信息,以了解应该在何处创建此类闭包——但本质上也是一样的

但是有一个更严重的问题,在你想做的高层。问题是,可变算术函数不能很好地与自动curry配合使用。例如,采用如下表达式:

(+123)

您将如何决定是否应按原样调用它,或者是否应将其转换为
(+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}>