为什么在Racket中以奇怪的方式定义foldl?

时间:2012-01-08 14:54:17

标签: recursion functional-programming scheme racket fold

在Haskell中,与许多其他函数语言一样,函数foldl被定义为例如foldl (-) 0 [1,2,3,4] = -10

这没关系,因为根据定义,foldl (-) 0 [1, 2,3,4]((((0 - 1) - 2) - 3) - 4)

但是,在Racket中,(foldl - 0 '(1 2 3 4))是2,因为Racket“智能地”计算如下:(4 - (3 - (2 - (1 - 0)))),确实是2。

当然,如果我们定义辅助功能翻转,就像这样:

(define (flip bin-fn)
  (lambda (x y)
    (bin-fn y x)))

然后我们可以在Racket中实现与Haskell相同的行为:而不是(foldl - 0 '(1 2 3 4))我们可以写:(foldl (flip -) 0 '(1 2 3 4))

问题是:为什么球拍中的foldl以这种奇怪的(非标准的,非直观的)方式定义,与其他语言不同?

4 个答案:

答案 0 :(得分:59)

  • Haskell定义不统一。在Racket中,两个折叠的函数具有相同的输入顺序,因此您只需将foldl替换为foldr并获得相同的结果。如果你使用Haskell版本,你会得到不同的结果(通常) - 你可以在两者的不同类型中看到这一点。

    (事实上,我认为为了进行适当的比较,你应该避免使用这两个类型变量都是整数的玩具数字例子。)

  • 这有很好的副产品,鼓励您根据语义差异选择foldlfoldr。我的猜测是,根据Haskell的顺序,您可能会根据操作选择。你有一个很好的例子:你已经使用了foldl,因为你想减去每个数字 - 而且这样的"显而易见的"选择它很容易忽视foldl在懒惰的语言中通常是一个糟糕的选择。

  • 另一个不同之处在于,Haskell版本比通常的方式更受限于Racket版本:它只在一个输入列表上运行,而Racket可以接受任意数量的列表。这使得输入函数具有统一的参数顺序变得更加重要。

  • 最后,假设Racket与许多其他函数式语言分离是错误的,因为折叠远非新技巧,而且Racket的根源远远超过Haskell(或者这些其他语言)。因此,问题可以采用另一种方式为什么Haskell的foldl以一种奇怪的方式定义?(不,(-)不是一个好的借口。)

历史更新:

由于这似乎一次又一次地困扰着人们,我做了一些腿法。这不是决定性的,只是我的二手猜测。如果您了解更多信息,甚至更好,请通过电子邮件向相关人员发送电子邮件并随时进行编辑。具体来说,我不知道做出这些决定的日期,因此以下列表大致有序。

  • 首先是Lisp,没有提及任何类型的"折叠。相反,Lisp的reduce非常不均匀,特别是考虑到它的类型。例如,:from-end是一个关键字参数,用于确定它是左扫描还是右扫描它使用不同的累加器函数,这意味着累加器类型取决于该关键字。这是其他黑客的补充:通常第一个值取自列表(除非您指定:initial-value)。最后,如果您没有指定:initial-value,并且列表为空,它实际上会将该函数应用于零参数以获得结果。

    所有这些意味着reduce通常用于其名称所暗示的内容:将值列表缩减为单个值,其中两种类型通常相同。这里的结论是,它提供了一种与折叠类似的目的,但它并不像折叠时使用的通用列表迭代结构那样有用。我猜这意味着reduce和后来的折叠操作之间没有强关系。

  • Lisp之后的第一个相关语言是ML。正如下面的newacct&#39}所述,在那里做出的选择是使用uniform types version(即Racket使用的)。

  • 下一个参考文献是Bird& Wadler的ItFP(1988),它使用different types(如在Haskell中)。但是,他们note in the appendix米兰达的类型相同(如在Racket中)。

  • 米兰达后来switched the argument order(即,从球拍订单转移到哈斯克尔订单)。具体来说,该文字说:

      

    警告 - foldl的这个定义与旧版Miranda的定义不同。这里的一个与Bird和Wadler(1988)中的相同。旧的定义有两个“op'逆转。

  • Haskell从Miranda那里拿走了很多东西,包括不同的类型。 (但当然我不知道日期,所以米兰达的改变可能是由于哈斯克尔。)无论如何,此时很清楚没有达成共识,因此上面相反的问题成立。

  • OCaml采用Haskell方向并使用different types

  • 我猜测"如何设计程序" (又名HtDP)大致在同一时期写成,他们选择了same type。但是,没有动机或解释 - 实际上,在练习之后,它被简单地称为one of the built-in functions

    球拍的fold operations的实施当然是"内置插件"这里提到的。

  • 然后来了SRFI-1,选择是使用相同类型的版本(如Racket)。约翰·大卫·斯通(John David Stone)的这一决定was question,他在SRFI的评论中指出

      

    注意:MIT Scheme和Haskell为他们的reducefold函数翻转F#的命令。

    奥林后来addressed this:他说的只是:

      

    好点,但我希望两个功能之间保持一致。       state-value first:srfi-1,SML       state-value last:Haskell

    特别注意他使用 state-value ,这表明一致的类型可能比运营商订单更重要的观点。

答案 1 :(得分:9)

“与任何其他语言不同”

作为一个反例,标准ML(ML是一种非常古老且有影响力的函数式语言)的foldl也是这样的:http://www.standardml.org/Basis/list.html#SIG:LIST.foldl:VAL

答案 2 :(得分:7)

球拍的foldlfoldr(以及SRFI-1's foldfold-right)拥有

的属性
(foldr cons null lst) = lst
(foldl cons null lst) = (reverse lst)

我推测因为这个原因选择了参数顺序。

答案 3 :(得分:3)

来自球拍documentationfoldl的说明:

(foldl proc init lst ...+) → any/c

提到了您的问题的两个兴趣点:

  

输入的任务从左到右遍历

  

foldl以恒定的空间处理lsts

我将推测这个实现的实现方式,为简单起见,只需一个列表:

(define (my-foldl proc init lst)
  (define (iter lst acc)
    (if (null? lst)
        acc
        (iter (cdr lst) (proc (car lst) acc))))
  (iter lst init))

如您所见,满足从左到右遍历和常量空间的要求(注意iter中的尾递归),但{{1>}的参数的顺序 {1}}从未在说明中指定过。因此,调用上述代码的结果将是:

proc

如果我们以这种方式指定(my-foldl - 0 '(1 2 3 4)) > 2 的参数顺序:

proc

然后结果将是:

(proc acc (car lst))

我的观点是,(my-foldl - 0 '(1 2 3 4)) > -10 的文档没有对foldl的参数的评估顺序做出任何假设,它只需要保证使用常量空间以及中的元素列表从左到右进行评估。

作为旁注,您只需写下以下内容即可获得表达式所需的评估顺序:

proc