在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
以这种奇怪的(非标准的,非直观的)方式定义,与其他语言不同?
答案 0 :(得分:59)
Haskell定义不统一。在Racket中,两个折叠的函数具有相同的输入顺序,因此您只需将foldl
替换为foldr
并获得相同的结果。如果你使用Haskell版本,你会得到不同的结果(通常) - 你可以在两者的不同类型中看到这一点。
(事实上,我认为为了进行适当的比较,你应该避免使用这两个类型变量都是整数的玩具数字例子。)
这有很好的副产品,鼓励您根据语义差异选择foldl
或foldr
。我的猜测是,根据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的评论中指出
奥林后来addressed this:他说的只是:注意:MIT Scheme和Haskell为他们的
reduce
和fold
函数翻转F#的命令。
好点,但我希望两个功能之间保持一致。 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)
球拍的foldl
和foldr
(以及SRFI-1's fold
和fold-right
)拥有
(foldr cons null lst) = lst
(foldl cons null lst) = (reverse lst)
我推测因为这个原因选择了参数顺序。
答案 3 :(得分:3)
来自球拍documentation,foldl
的说明:
(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