Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the Clojure equivalent of Scalaz Foldable's foldmap?

In the Scalaz trait Foldable we see the method foldMap with the following description

Map each element of the structure to a [[scalaz.Monoid]], and combine the results.

def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B

You can use it like this:

scala> List(1, 2, 3) foldMap {identity}
res1: Int = 6

scala> List(true, false, true, true) foldMap {Tags.Disjunction}
res2: scalaz.@@[Boolean,scalaz.Tags.Disjunction] = true

My question is: What is the Clojure equivalent of Scalaz Foldable's foldmap?

like image 662
hawkeye Avatar asked Feb 14 '14 11:02

hawkeye


2 Answers

I'm willing to be proven wrong, but I don't think Clojure has monoids, as such. However, check out this article that describes how you would create a monoid.

Specifically for your two examples, I would write:

(reduce + [1 2 3]) ; => 6

and

(some identity [true false true true]) ;=> true

Note that identity is not the Identity monoid. :-)

like image 22
Shepmaster Avatar answered Nov 13 '22 20:11

Shepmaster


By default Clojure has no monadic composition. For that you need libraries like algo.monads or fluokitten.

A monoid in Haskell and Skalaz is a class that implements three functions:

  • mempty returns the identity element
  • mappend combines two values of the same type
  • mconcat used to convert collections of that type to items and v.v.

Clojure has no fold function that invokes all three of those; reduce is the go-to higher order function for accumulating over a collection.

By default it takes 3 parameters: a reducer function, an accumulator and a collection. The reducer function is used to combine the accumulator and one item from the collection at a time. It need not accept identical types like mappend. The third is always a collection, which is why mconcat is not needed.

In the context of Clojure's 1.5 clojure.reducers and clojure.core/reduce, there is a monoid however: a function that returns it's identity element when called without parameters.

For instance:

(+) => 0 
(*) => 1
(str) => ""
(vector) => []
(list) => ()

This 'monoid' function is used as the reducer in the two parameter version of reduce; its 'monoidal identity' or mempty is called to create the initial accumulator.

(reduce + [1 2 3]) => (reduce + (+) [1 2 3]) => (reduce + 0 [1 2 3])

So if you want to translate the examples here, you need to find or make a function that has such a 'monoid' implementation to use it in the dual arity reduce.

For disjunction, Clojure has or:

(defmacro or
  "Evaluates exprs one at a time, from left to right. If a form
  returns a logical true value, or returns that value and doesn't
  evaluate any of the other expressions, otherwise it returns the
  value of the last expression. (or) returns nil."
  {:added "1.0"}
  ([] nil)
  ([x] x)
  ([x & next]
      `(let [or# ~x]
         (if or# or# (or ~@next)))))

It does have a 'monoid' implementation, ([] nil). However, or is implemented as a macro to support short circuiting, and can only be used within an expression to be expanded, not as a function parameter:

(reduce or [false true false true true])
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure.core/or, compiling

So we need a 'new' or that's a true function for disjunction. It should also implement a no-arity version returning nil:

(defn newor
  ([] nil)
  ([f s] (if f f s)))

So now we have a function with a 'monoid' implementation, and you can use it in the dual arity reduce:

(reduce newor [true false true true])
=> true

Seems a bit complicated until you understand why Clojure implemented or as a multiple arity macro

(or true false true true)
=> true
like image 113
17 revs Avatar answered Nov 13 '22 19:11

17 revs