2

Suppose I have a few functions of type Int => Option[Int]:

def foo(n: Int): Int => Option[Int] = {x => if (x == n) none else x.some}

val f0 = foo(0)
val f1 = foo(1)

I can compose them with >=> as follows:

val composed: Int => Option[Int] = Kleisli(f0) >=> Kleisli(f1)

Suppose now I need to compose all functions from a list:

val fs: List[Int => Option[Int]] = List(0, 1, 2).map(n => foo(n))

I can do it with map and reduce:

val composed: Int => Option[Int] = fs.map(f => Kleisli(f)).reduce(_ >=> _)

Can it (the composed above) be simplified ?

Michael
  • 41,026
  • 70
  • 193
  • 341

2 Answers2

3

If you want the composition monoid (as opposed to the "run each and sum the results" monoid), you'll have to use the Endomorphic wrapper:

import scalaz._, Scalaz._

val composed = fs.foldMap(Endomorphic.endoKleisli[Option, Int])

And then:

scala> composed.run(10)
res11: Option[Int] = Some(10)

The monoid for kleisli arrows only requires a monoid instance for the output type, while the composition monoid requires the input and output types to be the same, so it makes sense that the latter is only available via a wrapper.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Ok, so `Endomorphic.endoKleisli[M, A]` is applicable only for `A => M[A]` if `M` has a monad instance. What if I have `A => M[B]` ? – Michael Jul 13 '15 at 15:28
  • Do you mean that you want to compose `A => M[B]`, `B => M[C]`, `C => M[D]`, etc.? Then you'll need a type-aligned sequence (not currently in Scalaz). – Travis Brown Jul 13 '15 at 15:33
  • Thanks. Another question just to make sure I got it: `Endomorphic.endoKleisli[M, A]` is a function from `A => M[A]` to a wrapper, which has a a monoid instance. Is it correct ? – Michael Jul 13 '15 at 15:40
  • Yep, that's exactly it. – Travis Brown Jul 13 '15 at 15:40
  • Thanks. I believe I got it. So, now I can write a generic function to chain functions of type `A => M[A]`: e.g. `def chain[A, M[_] : Monad](fs: List[A => M[A]]) = fs foldMap (Endomorphic.endoKleisli[M, A])`. I would probably ask a new question about it. – Michael Jul 13 '15 at 16:00
  • @Michael By the way, there's been some interest in type-aligned sequences in the context of free monads lately, and there are a few Scala implementations floating around, if you're interested in this more general kind of composition chaining. – Travis Brown Jul 13 '15 at 16:28
1

[A] Kleisli[Option, A, A] is a Semigroup via Compose, so we can use foldMap1:

val composed: Int => Option[Int] = fs.foldMap1(f => Kleisli(f))

Interestingly this doesn't work, though if we pass the correct instance explicitly then it does:

scala> val gs = NonEmptyList(fs.head, fs.tail: _*)
gs: scalaz.NonEmptyList[Int => Option[Int]] = NonEmptyList(<function1>, <function1>, <function1>)
scala> gs.foldMap1(f => Kleisli(f))(Kleisli.kleisliCompose[Option].semigroup[Int])
res20: scalaz.Kleisli[Option,Int,Int] = Kleisli(<function1>)
scala> gs.foldMap1(f => Kleisli(f))(Kleisli.kleisliCompose[Option].semigroup[Int]).apply(1)
res21: Option[Int] = None

I'm not sure where the instance that seems to take priority is coming from.

lmm
  • 17,386
  • 3
  • 26
  • 37
  • 1
    it does not work as expected since `|+|` is not defined as `>=>`: e.g. `val f = Kleisli(f0) |+| Kleisli(f1); f(0) = Some(0)` instead of `None`. Thanks for the answer anyway. – Michael Jul 13 '15 at 10:48