← Back to context

Comment by s-zeng

10 hours ago

A neat fact about foldr on lists, unlike foldl, is that it actually passes control flow entirely to the accumulating function on each fold step. That means you can use foldr to implement arbitrary traversals of lists, including foldl' or list traversals that exit early. See https://github.com/quchen/articles/blob/master/useful_techni...

In other words, `foldr` (flipped about) takes a list to its Church encoding: since it just ‘replaces the constructors’ (`:` becomes `f`, `[]` becomes `z`), it doesn't lose any information from the list: if you can write a function by recursing on a list then you can write it using `foldr`.

Yes, because foldr is equivalent to for_, that is, iterating over a container and performing an effectful action (what in other languages would be called "doing something") for each element. Uses of foldr can always be rewritten to uses of for_, and I find things much clearer in terms of for_!

(This is explained in my article "foldl traverses with State, foldr traverses with anything": https://h2.jaguarpaw.co.uk/posts/foldl-traverses-state-foldr...)

  • Well, `foldr` is (up to interdefinability) just the implementation of `for_` (or `traverse`) in the case of lists, in the same way that `map` is the implementation of `fmap`. The point of `traverse` is that it's provided by the functor.

    The question of whether to use the most generic (for consistency) or the least generic (for directness) name for a function is the old bugbear of the Prelude :)

At some point doesn't it become easier to write the function explicitly? As in

    go [] = ...
    go head:remainder = ...

instead of hacking it together with a fold?

  • If you don’t already know about folds, arguably yes.

    If you do, then a quick glance at whether you’re using foldl’ or foldr tells you about what the function is allowed to do, which cuts down a little on comprehension.

    List traversals are typically compact enough that there’s not a huge difference either way.

    One advantage of using a fold, even in these cases, is that newcomers to Haskell often get so carried away with (and confused by) the power of pattern matching that they’ll write bizarre overly-complicated list traversals by hand, when a simpler mechanism exists that they just haven’t yet internalized.

  • There's a relatively popular point of view amongst Haskell programmers that explicit recursion is the goto of functional programming; a dedicated folding or traversing function provides more clarity on what exactly the function intends to do. The extreme end of this is recursion schemes and memes like zygohistomorphic prepomorphisms, which are almost certainly overkill on lists proper but might be useful when traversing bigger recursive structures. Personally, I almost always prefer to find a monoid to map the list elements into, and use `fold :: (Monoid m, Foldable t) => t m -> m`. It's essentially the equivalent of using `sum()` instead of `reduce()` in python

  • One of the links (on Fusion) points out that GHC in particular has a lot of optimizations and rewrite rules for folds and many Prelude functions written as simple recursion also include optimized fold representations to suggest to various stages of GHC's optimizer.

    As with so many such topics it seems an interesting spectrum between clarity and/or aesthetics and potential performance optimizations. Especially because it often seems like one of those "learning curve flips the clarity/aesthetics preferences" because at some point of familiarity folds can be faster to read than trying to reason through an explicitly written recursion.

  • I think once you become comfortable with a fold, and of thinking in that manner, it actually becomes easier with a fold.