是否可以实现自动curry到Lisp系列语言?

时间:2012-06-27 03:15:49

标签: clojure lisp scheme common-lisp currying

也就是说,当你使用只有一个参数的> 1 arity调用一个函数时,它应该,而不是显示错误,curry该参数并返回生成的函数,减少arity。这是否可以使用Lisp的宏?

6 个答案:

答案 0 :(得分:13)

如果你想要一个有用的结果,这是可能的,但并不容易。

  • 如果你想要一种总是做简单currying的语言,那么实现起来很容易。您只需将多个输入的每个应用程序转换为嵌套应用程序,并将多个参数的函数转换为相同的应用程序。使用Racket's语言工具,这是一个非常简单的练习。 (在其他lisps中,你可以通过代码中某些宏来获得类似的效果。)

    (顺便说一下,我在Racket之上有一种语言可以做到这一点。它充分发挥了自动语言的可爱性,但它并不是实用的。)

    但是,它不太有用,因为它只适用于一个参数的函数。您可以通过一些黑客攻击使其变得有用,例如,将您的语言的其余部分视为外语,并提供表单来使用它。另一种方法是为您的语言提供有关周围lisp功能的arity信息。这些都需要更多的工作。

  • 另一种选择是检查每个应用程序。换句话说,你转每个

    (f x y z)
    

    进入检查f的arity的代码,如果没有足够的参数,将创建一个闭包。这本身并不太难,但会导致巨大的开销价格。您可以尝试使用类似的一些关于函数arities的信息,这些函数可以在宏级别中使用,以了解应该在何处创建这样的闭包 - 但这很难以基本相同的方式进行。

但是,在你想做的事情的高层次上,存在一个更为严重的问题。事实是变量功能只是不能很好地与自动currying。例如,采用如下表达式:

(+ 1 2 3)

您如何决定是否应按原样调用,或者是否应将其翻译为((+ 1 2) 3)?看来这里有一个简单的答案,但是这个怎么样? (翻译成你最喜欢的lisp方言)

(define foo (lambda xs (lambda ys (list xs ys))))

在这种情况下,您可以通过多种方式拆分(foo 1 2 3)。还有一个问题是你如何处理:

(list +)

这里你有+作为表达式,但是你可以决定这与将它应用于适合+ sity的零输入相同,但是你如何编写一个表达式来评估添加功能? (旁注:ML和Haskell通过没有固定功能来解决这个问题......)

其中一些问题可以通过决定每个“真正的”应用程序必须具有parens来解决,因此永远不会应用+本身。但是,这种语言失去了很多可爱性,你仍然有问题要解决......

答案 1 :(得分:9)

在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

来自Racket的documentation

  

[curry]返回一个程序,它是proc的curried版本。首次应用生成的过程时,除非给出它可以接受的最大参数数,否则结果是接受其他参数的过程。

您可以轻松实现一个在定义新过程时自动使用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

答案 2 :(得分:2)

简短的回答是肯定的,尽管不容易。

你可以把它作为一个宏来填充partial 中的每一个电话,尽管只是在有限的背景下。 Clojure有一些功能可以使这个相当困难,例如变量arity函数和dynamit调用。 Clojure缺少一个正式的类型系统来具体决定何时调用不再有参数并且实际上应该被调用。

答案 3 :(得分:1)

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中实现它。也可以使用宏来实现它,但似乎宏会让它更加混乱非常基本的东西。

答案 4 :(得分:1)

正如Alex W所述,Common Lisp Cookbook does give an example是针对Common 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))))

自动调整不应该那么难实现,所以我对它进行了一次破解。请注意,以下内容未经过广泛测试,并且未检查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}>

答案 5 :(得分:1)

当然,您只需要为您的语言确定确切的语义,然后实现自己的 loader ,它将您的源文件转换为实现语言。

你可以,例如将每个用户函数调用(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之上,以实现自动currying方案(禁止varargs函数)课程)。

您还需要定义自己的基元,将varargs函数转换为例如(+)为二进制文件,并将其应用程序设置为使用fold,例如(+ 1 2 3 4) ==&gt; (fold (+) (list 1 2 3 4) 0)或其他内容 - 或者只是在您的新语言中进行(+ 1 2 3 4)非法调用,期望其用户自己编写fold表单。

这就是我所说的“决定......语言的语义”。

加载器可以像将文件内容包装到对的调用一样简单 - 根据您的问题,您必须实现该操作。