4

In foldl definition possible wrong in SML/NJ 110.75, I found that the relation foldl (op -) 2 [1] = foldr (op -) 2 [1] holds. But when I tried the above in Haskell I found that the above relation rewritten in Haskell as foldl (-) 2 [1] == foldr (-) 2 [1] doesn't hold. Why is this? Does Haskell have different definition for fold than SML/NJ?

Thanks

Community
  • 1
  • 1
Dragno
  • 3,027
  • 1
  • 27
  • 41
  • 1
    It's not wrong, it is just different. – Ingo Dec 08 '13 at 11:19
  • That is what I ask. Read the last sentence "Does Haskell have different definition for fold than SML/NJ"? – Dragno Dec 08 '13 at 11:23
  • 1
    The answer is "yes, obviously". – Ingo Dec 08 '13 at 11:24
  • Can you put your answer as answer and not as comment in order to rate it and possibly give it a tick? And by the way can you notice the differences? Thanks. – Dragno Dec 08 '13 at 11:28
  • It is too easy to get a tick for it. And, you can look up the definition of foldr in Haskell with Hoogle. You'll see it has a different type, and the implementation shows you that it is right associative. Hence `foldr (-) 1 [2,3]` is 2-(3-1), and not (1-2)-3. – Ingo Dec 08 '13 at 11:31
  • You will take the tick for the answer not me for the question. And I think the problem is not the foldr but foldl. – Dragno Dec 08 '13 at 11:33
  • 1
    The reason the relation doesn't hold in Haskell is that with `foldr` your're doing `2 - 1` vs. `1 - 2` with `foldl`. Notice that the 2 functions have different signatures in Haskell, but the same signature in SML/NJ. – ja. Dec 08 '13 at 12:53

4 Answers4

8

In ML, both folds have the same type signature:

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

whereas in Haskell they're different:

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

so Haskell's foldl is necessarily doing something different with the operation it's been given.

Similarities

The two languages agree on both the type and the value computed by foldr - a list folded into a value by moving righwards along the list, bracketed from the right hand end:

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

Differences

First, ML has

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

So ML's foldl is a left fold in the sense that it folds the list leftwards instead of rightwards.

whereas in Haskell, you have

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

In haskell, foldl is a left fold in the sense that it puts the initial value at the left and brackets the list from the left, but retains its order.

Your example

With a list with just a single element, ML does f(x1,init) which gives you x1 - init which happens to be the same as foldr's xn - init because the first and last elements are the same.

Conversely, Haskell does f(init,x1) which gives you init - x1. That's why you get the opposite answer.

Slightly longer example

ML's foldl:

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

ML/Haskell's foldr:

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

Haskell's foldl:

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

Conclusion

Yes the two definitions are different for foldl. ML's left means opposite order of elements, whereas Haskell's left means opposite order of bracketing.

This isn't a big problem as long as you remember which one you're using. (If the types of init and x1 are different, the type checker will tell you when you get it wrong.)

not my job
  • 642
  • 10
  • 21
  • 1
    Should that be a `foldr` instead of `foldl` in the SML code for the second example (`foldr` example) in the /slightly longer example/ section? --.> `... or foldl (op -) 100 [1,2,3,4]` – spazm Apr 23 '18 at 05:03
  • @spazm You're quite right - thanks for pointing out the typo. – not my job May 23 '18 at 21:15
5

Does this help?

mlFoldl :: (a -> b -> b) -> b -> [a] -> b
mlFoldl f = foldl (flip f)
Tom Ellis
  • 9,224
  • 1
  • 29
  • 54
3

Long story short, they are essentially the same, with one minor difference: the order of the arguments passed to the operator (the combining function you pass to fold) are flipped. And since subtraction is not commutative, it will produce different results.

In Haskell (as well as OCaml, C++, Clojure, Common Lisp, Erlang, F#, JavaScript, PHP, Python, Ruby, Scala, and many others), for foldl, the supplied function's first argument is the initial value, or the "folded value so far", while the second argument is an element from the list.

However, in Standard ML, the supplied function's first argument is the element from the list, and the second argument is the initial value, or the "folded value so far".

Neither is "correct" or "incorrect". The order of arguments is purely a design decision. The way Haskell does it is more commonly used today across languages. And in a certain "graphical" way of looking at folding, it makes more sense. Why did SML define theirs the way they did? I am not sure. Perhaps so that the signatures of foldl and foldr will be the same.

newacct
  • 119,665
  • 29
  • 163
  • 224
2

Expanding on Some Other Guy's answer:

From the 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

so foldl (-) 2 [1] is (2 - 1) and foldr (-) 2 [1] is (1 - 2)

From the 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. 

so foldl (op -) 2 [1] is fxn - init or 1 - 2, and foldr (op -) 2 [1] is fx1 - init. It is still 1 - 2, but only by coincidence. The answers diverge with a longer list, but not as much as the answers between Haskell and SML.

ben rudgers
  • 3,647
  • 2
  • 20
  • 32