【问题标题】:Is it possible to implement auto-currying to the Lisp-family languages?是否可以对 Lisp 系列语言实现自动柯里化?
【发布时间】:2012-06-27 03:15:49
【问题描述】:

也就是说,当你调用一个只有一个参数的具有>1 arity 的函数时,它应该而不是显示错误,而是对该参数进行curry 并返回具有减少arity 的结果函数。使用 Lisp 的宏可以做到这一点吗?

【问题讨论】:

  • 您必须在 Lisp 之上实现一种类型化语言(使用宏很容易实现),但要将这种语言与 Lisp 的其余部分混合使用并不容易。

标签: clojure lisp scheme common-lisp currying


【解决方案1】:

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

  • 如果您想要一种总是进行简单柯里化的语言,那么实现很容易。您只需将具有多个输入的每个应用程序转换为嵌套应用程序,对于具有多个参数的函数也是如此。使用Racket's 语言设施,这是一个非常简单的练习。 (在其他 lisps 中,您可以通过围绕要使用它的代码的一些宏来获得类似的效果。)

    (顺便说一句,我在 Racket 之上有一种语言可以做到这一点。它具有自动柯里化语言的全部可爱之处,但它并不实用。)

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

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

    (f x y z)
    

    到检查f 的arity 的代码中,如果没有足够的参数,将创建一个闭包。这本身并不太难,但会导致显着的间接费用。您可以尝试使用类似的技巧,获取一些关于您将在宏级别使用的函数的一些信息,以了解应该在哪里创建此类闭包——但这基本上是相同的方式。

但是有一个更严重的问题,在你想做的事情的高层次上。问题是可变参数函数不能很好地与自动柯里化配合使用。例如,采用如下表达式:

(+ 1 2 3)

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

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

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

(list +)

这里你有 + 作为表达式,但你可以决定这与将它应用于适合 +s arity 的零输入相同,但是你如何编写一个计算为加法函数的表达式? (旁注:ML 和 Haskell 通过不使用无效函数来“解决”这个问题……)

其中一些问题可以通过决定每个“真实”应用程序都必须有括号来解决,因此+ 本身永远不会被应用。但这失去了自动柯里化语言的可爱之处,你仍然有问题需要解决......

【讨论】:

    【解决方案2】:

    在 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 的柯里化版本。当第一次应用生成的过程时,除非给定它可以接受的最大参数数量,否则结果是一个接受附加参数的过程。

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

    【讨论】:

    • 总体上很好的答案,但你提供的小片段证明非常有用,所以我不能接受另一个答案,但这个。
    • 尽管非常有趣,但两个 sn-ps 都不会自动柯里化。
    【解决方案3】:

    简短的回答是肯定的,但并不容易。

    您可以将其实现为一个将每个调用都包含在partial 中的宏,尽管仅限于有限的上下文。 Clojure 有一些特性会使这变得相当困难,例如可变参数函数和动态调用。 Clojure 缺乏一个正式的类型系统来具体决定调用何时不能有更多参数并且应该实际调用。

    【讨论】:

      【解决方案4】:

      正如 Alex W 所指出的,Common Lisp Cookbook does give an example 是 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}>
      

      【讨论】:

        【解决方案5】:

        当然,您只需为您的语言确定准确的语义,然后实现您自己的 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 Scheme(这当然会禁止可变参数函数)。

        您还需要定义自己的原语,将可变参数函数转换为例如(+) 转为二进制,并将其应用程序转为使用 fold,例如(+ 1 2 3 4) ==> (fold (+) (list 1 2 3 4) 0) 或其他东西 - 或者只是在 您的新语言 中调用 (+ 1 2 3 4) 非法,期望其用户自己编写 fold 表单。

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

        加载器可以像将文件内容包装到对的调用一样简单——然后你必须根据你的问题来实现它。

        【讨论】:

          【解决方案6】:

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

          【讨论】:

          • “arity”是 Prolog 中的常用术语。具有相同名称和不同arities的函数(谓词)被认为是不同的。
          猜你喜欢
          • 2011-03-27
          • 1970-01-01
          • 1970-01-01
          • 2013-11-09
          • 2011-01-08
          • 2013-06-03
          • 2010-10-15
          • 1970-01-01
          相关资源
          最近更新 更多