cats icon indicating copy to clipboard operation
cats copied to clipboard

Bug? Behaviour of SemigroupK instance for Kleisli

Open leigh-perry opened this issue 5 years ago • 2 comments

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?

leigh-perry avatar Nov 18 '19 22:11 leigh-perry

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.

keiSunagawa avatar Jan 24 '20 15:01 keiSunagawa

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

strong-zero avatar Jan 28 '20 03:01 strong-zero