2

trying to implement Kleisli category for a made-up Partial type in Scala (reading Bartosz Milewski's "category theory for programmers", that's exersize for chapter 4)

object Kleisli { type Partial[A, B] = A => Option[B] implicit class KleisliOps[A, B](f1: Partial[A, B]) { def >=>[C](f2: Partial[B, C]): Partial[A, C] = (x: A) => for { y <- f1(x) z <- f2(y) } yield z def identity(f: Partial[A, B]): Partial[A, B] = x => f(x) } val safeRecip: Partial[Double, Double] = { case 0d => None case x => Some(1d / x) } val safeRoot: Partial[Double, Double] = { case x if x < 0 => None case x => Some(Math.sqrt(x)) } val safeRootRecip: Partial[Double, Double] = safeRoot.>=>(safeRecip) safeRootRecip(1d) safeRootRecip(10d) safeRootRecip(0d) } 

IDE (IntelliJ) shows no errors, but when I run this snippet, I get:

Error:(27, 57) value >=> is not a member of $line5.$read.$iw.$iw.Kleisli.Partial[Double,Double] val safeRootRecip: Partial[Double, Double] = safeRoot.>=>(safeRecip) 

Defining >=> outside of implicit class works fine. What could be the reason?

0

1 Answer 1

6

@sinanspd was right. In Dotty the code seems to compile: https://scastie.scala-lang.org/n17APWgMQkWqy93ct2cghw

Manually resolved

val safeRootRecip: Partial[Double, Double] = KleisliOps(safeRoot).>=>(safeRecip) 

compiles but compiler doesn't find this conversion itself

Information: KleisliOps{<null>} is not a valid implicit value for App.safeRoot.type => ?{def >=> : ?} because: type mismatch; found : App.safeRoot.type (with underlying type App.Partial[Double,Double]) required: App.Partial[A,Double] (which expands to) A => Option[Double] val safeRootRecip: Partial[Double, Double] = safeRoot.>=>(safeRecip) 

It seems type parameter A is not inferred.

(By the way, here Martin Odersky explains why presence of implicit conversions in language makes type inference worse: https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388)

Try to make Partial covariant with respect to B and (especially) contravariant with respect to A (similarly to A => Option[B] being covariant with respect to B and contravariant with respect to A)

type Partial[-A, +B] = A => Option[B] 

Then the code seems to compile.

Another workaround is to replace implicit conversions (X => Y, KleisliOps) with a type class (MyTransform) and implicit conversion (myConversion) defined in terms of this type class (sometimes this helps with implicit conversions)

trait MyTransform[X, Y] { def transform(x: X): Y } implicit def myConversion[X, Y](x: X)(implicit mt: MyTransform[X, Y]): Y = mt.transform(x) type Partial[A, B] = A => Option[B] implicit def partialToKleisliOps[A, B]: MyTransform[Partial[A, B], KleisliOps[A, B]] = f1 => new KleisliOps(f1) class KleisliOps[A, B](f1: Partial[A, B]) { def >=>[C](f2: Partial[B, C]): Partial[A, C] = (x: A) => for { y <- f1(x) z <- f2(y) } yield z def identity(f: Partial[A, B]): Partial[A, B] = x => f(x) } 
Sign up to request clarification or add additional context in comments.

1 Comment

Small typo: "(especially) contravariant with respect to B" should say "A" instead.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.