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

2

6

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?

Shortterm answered 14/2, 2014 at 11:37 Comment(1)
Related: How to write monoid protocol in Clojure?Briney
C
8

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
Cramp answered 14/2, 2014 at 11:37 Comment(1)
or is not implemented as a macro to support variable arity ; functions can have multiple arity too. oris implemented as a macro to support lazy evaluation.Holle
M
2

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. :-)

Mae answered 14/2, 2014 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.