Search code examples
clojure

How do we do both left and right folds in Clojure?


Reduce works fine but it is more like fold-left. Is there any other form of reduce that lets me fold to right ?


Solution

  • Let's look at a possible definition of each:

    (defn foldl [f val coll]
      (if (empty? coll) val
        (foldl f (f val (first coll)) (rest coll))))
    
    (defn foldr [f val coll]
      (if (empty? coll) val
        (f (foldr f val (rest coll)) (first coll))))
    

    Notice that only foldl is in tail position, and the recursive call can be replaced by recur. So with recur, foldl will not take up stack space, while foldr will. That's why reduce is like foldl. Now let's try them out:

    (foldl + 0 [1 2 3]) ;6
    (foldl - 0 [1 2 3]) ;-6
    (foldl conj  [] [1 2 3]) ;[1 2 3]
    (foldl conj '() [1 2 3]) ;(3 2 1)
    
    (foldr + 0 [1 2 3]) ;6
    (foldr - 0 [1 2 3]) ;-6
    (foldr conj  [] [1 2 3]) ;[3 2 1]
    (foldr conj '() [1 2 3]) ;(1 2 3)
    

    Is there some reason you want to fold right? I think the most common usage of foldr is to put together a list from front to back. In Clojure we don't need that because we can just use a vector instead. Another choice to avoid stack overflow is to use a lazy sequence:

    (defn make-list [coll]
      (lazy-seq
        (cons (first coll) (rest coll))))
    

    So, if you want to fold right, some efficient alternatives are

    1. Use a vector instead.
    2. Use a lazy sequence.
    3. Use reduced to short-circuit reduce.
    4. If you really want to dive down a rabbit hole, use a transducer.