cats
cats copied to clipboard
Bug? Behaviour of SemigroupK instance for Kleisli
The test code:
val f1: Endo[Int] = i => i / 4
val f2: Endo[Int] = i => i * 2
val f3: Endo[Int] = i => i + 3
println(s"function composition: ${(f1 compose f2 compose f3) (5)}")
val fcombined: Endo[Int] = f1 <+> f2 <+> f3
println(s"function SemigroupK: ${fcombined(5)}")
val k1 = Kleisli[List, Int, Int](i => List(i / 4))
val k2 = Kleisli[List, Int, Int](i => List(i * 2))
val k3 = Kleisli[List, Int, Int](i => List(i + 3))
println(s"Kleisli composition: ${(k1 compose k2 compose k3).run(5)}")
val kcombined: Kleisli[List, Int, Int] = k1 <+> k2 <+> k3
println(s"Kleisli SemigroupK: ${kcombined(5)}")
yields:
function composition: 4
function SemigroupK: 4
Kleisli composition: List(4)
Kleisli SemigroupK: List(1, 10, 8)
Kleisli
for SemigroupK
is implemented in KleisliSemigroupK
:
override def combineK[B](x: Kleisli[F, A, B], y: Kleisli[F, A, B]): Kleisli[F, A, B] =
Kleisli(a => F.combineK(x.run(a), y.run(a)))
which yields the unexpected (to me) behaviour for Kleisli[List, A, *]
I can yield my expected Kleisli composition behaviour, by using an explicit typeclass instance:
override def combineK[A](x: Kleisli[F, A, A], y: Kleisli[F, A, A]): Kleisli[F, A, A] =
x compose y
But firstly, is the existing behaviour considered a bug?
Kleisli is like Monad Transformer.
Monad Transformer has type signature M[F[_], A]
. e.g. OptionT, EitherT, StateT
Those Monad Transformers TypeClass instances has inner type F instance. and uses.
from this, I think that right Kleisli behaviour.
PS. ReaderT is alais to Kleisli.
Faced to a similar problem recently. It turned out we have to use endoSemigroupK
explicitly:
val kcombined: Kleisli[List, Int, Int] = Kleisli.endoSemigroupK[List].combineK(
Kleisli.endoSemigroupK[List].combineK(k1, k2), k3)
It seems unclear to me this is ideal. But at least it is intentional: https://github.com/typelevel/cats/pull/1098