【问题标题】:Different fold in Haskell and SML/NJHaskell 和 SML/NJ 中的不同折叠
【发布时间】:2013-12-25 12:13:36
【问题描述】:

foldl definition possible wrong in SML/NJ 110.75 中,我发现关系foldl (op -) 2 [1] = foldr (op -) 2 [1] 成立。但是当我在 Haskell 中尝试上述方法时,我发现在 Haskell 中重写为 foldl (-) 2 [1] == foldr (-) 2 [1] 的上述关系不成立。为什么是这样? Haskell 对 fold 的定义是否与 SML/NJ 不同?

谢谢

【问题讨论】:

  • 没有错,只是不同而已。
  • 这就是我的要求。阅读最后一句“Haskell 对 fold 的定义是否与 SML/NJ 不同”?
  • 答案是“是的,很明显”。
  • 您能否将您的答案作为答案而不是评论,以便对其进行评分并可能给它打勾?顺便说一句,你能注意到这些差异吗?谢谢。
  • 这种关系在 Haskell 中不成立的原因是 foldr 你正在做 2 - 11 - 2foldl。请注意,这 2 个函数在 Haskell 中具有不同的签名,但在 SML/NJ 中具有相同的签名。

标签: haskell smlnj fold


【解决方案1】:

在 ML 中,两个折叠具有相同的类型签名:

val foldl : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b
val foldr : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b

而在 Haskell 中它们是不同的:

foldl :: (a -> b -> a) -> a -> [b] -> a
foldr :: (a -> b -> b) -> b -> [a] -> b

因此,Haskell 的 foldl 必然会对其给出的操作做一些不同的事情。

相似之处

两种语言在由foldr 计算的类型和值上都达成一致 - 一个列表通过沿列表向右移动折叠成一个值,从右端开始括起来:

foldr f init [x1, x2, ..., xn]    
 ==> f(x1, f(x2, ..., f(xn, init)...))

区别

首先,机器学习有

foldl f init [x1, x2, ..., xn]
 ==> f(xn,...,f(x2, f(x1, init))...)

所以 ML 的 foldl 是左折叠,因为它将列表向左折叠而不是向右折叠。

而在 Haskell 中,你有

foldl f init [x1,x2,.....,xn]
 ==> f(f(...f(f(init,x1),x2),.....),xn)

在 haskell 中,foldl 是左折叠,因为它将初始值放在左侧,并将列表从左侧括起来,但保留其顺序。

你的例子

对于只有一个元素的列表,ML 执行f(x1,init),它给你x1 - init,这恰好与foldrxn - init 相同,因为第一个和最后一个元素是相同的。

相反,Haskell 执行f(init,x1),它为您提供init - x1。这就是为什么你得到相反的答案。

略长的例子

ML 的foldl

foldl (op -) 100 [1,2,3,4]
 ==> 4 - (3 - (2 - (1 - 100)))
 ==> 102

ML/Haskell 的foldr:

foldr (-) 100 [1,2,3,4]    or    foldr (op -) 100 [1,2,3,4]
 ==> 1 - (2 - (3 - (4 - 100)))
 ==> 98

Haskell 的foldl:

foldl (-) 100 [1,]
 ==> (((100 - 1) - 2) - 3) - 4
 ==> 90

结论

是的,foldl 的两个定义不同。 ML 的 left 表示元素的顺序相反,而 Haskell 的 left 表示括号的顺序相反。

只要您记得您使用的是哪一个,这不是一个大问题。 (如果initx1的类型不同,类型检查器会在你弄错时告诉你。)

【讨论】:

  • 在 /slightly long example/ 部分的第二个示例(foldr 示例)的 SML 代码中应该是 foldr 而不是 foldl? --.> ... or foldl (op -) 100 [1,2,3,4]
  • @spazm 你说的很对 - 感谢您指出错字。
【解决方案2】:

这有帮助吗?

mlFoldl :: (a -> b -> b) -> b -> [a] -> b
mlFoldl f = foldl (flip f)

【讨论】:

    【解决方案3】:

    长话短说,它们本质上是相同的,有一个细微的差别:传递给操作符的参数的顺序(你传递给 fold 的组合函数)被翻转了。而且由于减法是不可交换的,所以会产生不同的结果。

    在 Haskell(以及 OCaml、C++、Clojure、Common Lisp、Erlang、F#、JavaScript、PHP、Python、Ruby、Scala 等)中,对于 foldl,提供的函数的第一个参数是初始值,或“到目前为止的折叠值”,而第二个参数是列表中的一个元素。

    但是,在标准 ML 中,提供的函数的第一个参数是列表中的元素,第二个参数是初始值,或“到目前为止的折叠值”。

    既不是“正确”也不是“不正确”。参数的顺序纯粹是一个设计决定。 Haskell 的做法如今在跨语言中更为常用。并且以某种“图形化”的方式看待折叠,它更有意义。为什么 SML 以他们的方式定义他们的?我不知道。也许这样foldlfoldr 的签名会相同。

    【讨论】:

      【解决方案4】:

      扩展其他人的回答:

      来自Haskell Wiki

      -- if the list is empty, the result is the initial value z; else
      -- apply f to the first element and the result of folding the rest
      foldr f z []     = z 
      foldr f z (x:xs) = f x (foldr f z xs) 
      
      -- if the list is empty, the result is the initial value; else
      -- we recurse immediately, making the new initial value the result
      -- of combining the old initial value with the first element.
      foldl f z []     = z                  
      foldl f z (x:xs) = foldl f (f z x) xs
      

      所以foldl (-) 2 [1] 是 (2 - 1) 而foldr (-) 2 [1] 是 (1 - 2)

      来自SML Basis Library

      foldl f init [x1, x2, ..., xn]
          returns
      
          f(xn,...,f(x2, f(x1, init))...)
      
          or init if the list is empty.
      
      foldr f init [x1, x2, ..., xn]
          returns
      
          f(x1, f(x2, ..., f(xn, init)...))
      
          or init if the list is empty. 
      

      所以 foldl (op -) 2 [1] 是 fxn - init 或 1 - 2,foldr (op -) 2 [1] 是 fx1 - init。它仍然是 1 - 2,但只是巧合。答案在更长的列表中出现分歧,但没有 Haskell 和 SML 之间的答案那么多。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-23
        • 2012-11-10
        • 1970-01-01
        • 1970-01-01
        • 2012-03-26
        • 1970-01-01
        • 2017-01-30
        • 2011-11-24
        相关资源
        最近更新 更多