Problem with given instances writing MTL style code with Scala cats - scala

I am trying to write some Scala code to have custom behaviour in an mtl style. For example, in order to expose the "write to DB" functionality abstracting over the specific effect I wrote my own type class:
trait CanPersist[M[_]]:
def persistToDB[A](a: A): M[Unit]
given CanPersist[IO] with
def persistToDB[A](a: A): IO[Unit] = IO(???) // Write to DB
The IO instance can be easily implemented but what I'm interested in is automatically providing the instance for any IO-based monad stack:
// If a Transformer wraps a Monad that can persist then it can persist too
given persistTA[M[_]: CanPersist: Monad, T[_[_], _]: MonadTransformer]:
CanPersist[[A] =>> T[M, A]] with
def persistToDB[A](a: A): T[M, Unit] =
summon[MonadTransformer[T]].lift(summon[CanPersist[M]].persistToDB(a))
The problem is apparently cats does not define its own MonadTransformer type class; luckily its pretty straightforward to write your own:
trait MonadTransformer[T[_[_], _]]:
def lift[M[_]: Monad, A](ma: M[A]): T[M, A]
// A Monad Transformer is a Monad if it wraps a Monad
given monadTA[M[_]: Monad, T[_[_], _]: MonadTransformer]: Monad[[A] =>> T[M, A]] with
def pure[A](a: A): T[M, A] = ??? // implementations are not relevant
def flatMap[A, B](fa: T[M, A])(f: A => T[M, B]): T[M, B] = ???
def tailRecM[A, B](a: A)(f: A => T[M, Either[A, B]]): T[M, B] = ???
// Both WriterT and EitherT are Monad Transformers
given writerMT[L: Monoid]: MonadTransformer[[M[_], A] =>> WriterT[M, L, A]] with
def lift[M[_]: Monad, A](ma: M[A]): WriterT[M, L, A] =
WriterT.liftF(ma)
given eitherMT[Err]: MonadTransformer[[M[_], A] =>> EitherT[M, Err, A]] with
def lift[M[_]: Monad, A](ma: M[A]): EitherT[M, Err, A] =
EitherT.liftF(ma)
And now onto the code that actually uses the CanPersist functionality:
def saveIntString[M[_]: Monad]
(int: Int, string: String)
(using P:CanPersist[M])
: M[String] =
for {
_ <- P.persistToDB(int)
_ <- P.persistToDB(string)
} yield "done"
val res: WriterT[IO, String, String] = saveIntString(2, "test")
// Does not compile:
// no implicit argument of type CanPersist[M] was found for parameter P of method saveIntString
// where: M is a type variable with constraint <: [V] =>> cats.data.WriterT[cats.effect.IO, String, V]
// I found:
// persistTA[M, T]
// But given instance persistTA does not match type CanPersist[M].
The problem is the compiler apparently can not derive the correct instances; this confuses me though. I thought the compiler would be able to derive the correct instance:
WriterT has a Transformer instance
IO has a CanPersist instance
Since WriterT is a Transformer and IO a monad that can persist WriterT[IO, _, _] should also have a CanPersist instance
Is there a way to define the described Transformer typeclass this way? Can the compiler derive such instances or is it impossible in Scala?

Problems with inference seem to be one of the reasons why the particular MTL implementation that you linked is relying on traits such as MonadPartialOrder instead of MonadTransformer-typeclasses.
Basically, what happens here is this: When you want to get from F to G
The MonadPartialOrder-approach asks for a bridge from F to G
Your approach asks to deconstruct G into [X] =>> T[M, X], then find a fancy universal bridge-builder T, and then use that contraption to build a bridge from F to ([X] =>> T[M, X]).
Thus, cats.mtl's approach is much simpler, and far less demanding of the inference algorithm. That's why cats.mtl works, whereas your approach doesn't.
I'll first sketch how your example can be fixed, then I'll speculate a little about why your approach does not work.
A solution with MonadPartialOrder
Here is how I'd try to approach your problem using the MonadPartialOrder from cats.mtl:
import cats.data.WriterT
import cats.syntax.all._
import cats.mtl.MonadPartialOrder
trait CanPersist[M[_]]:
def persistToDB[A](a: A): M[Unit]
given CanPersist[IO] with
def persistToDB[A](a: A): IO[Unit] = IO(???) // Write to DB
given persistTA[F[_]: CanPersist: Monad, G[_]]
(using mpo: MonadPartialOrder[F, G]): CanPersist[G] with
def persistToDB[A](a: A): G[Unit] =
mpo(summon[CanPersist[F]].persistToDB(a))
def saveIntString[M[_]: Monad]
(int: Int, string: String)
(using P:CanPersist[M])
: M[String] =
for {
_ <- P.persistToDB(int)
_ <- P.persistToDB(string)
} yield "done"
def res: WriterT[IO, String, String] = saveIntString(2, "test")
#main def main(): Unit =
println("Run it with 'sbt clean compile run'")
The basic idea is to use MonadPartialOrder[F, G] to get from F to G, instead of requiring a MonadTransformer[T] to get from F to [X] =>> T[F, X].
This compiles and runs just fine on Scala 3.1.2, here is a complete build.sbt, if you want to try it out:
import Dependencies._
ThisBuild / scalaVersion := "3.1.2"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.foobarbaz"
ThisBuild / organizationName := "example"
lazy val root = (project in file("."))
.settings(
name := "cats-mtl-so-question-72407103",
scalacOptions += "-explaintypes",
libraryDependencies += scalaTest % Test,
libraryDependencies += "org.typelevel" %% "cats-core" % "2.7.0",
libraryDependencies += "org.typelevel" %% "cats-mtl" % "1.2.1",
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.4-389-3862cf0",
)
Why your approach does not work
The logic in your explanation seems fine to me, so I would say that the compiler currently cannot infer the required typeclasses. The reason why your solution does not work (whereas cats.mtl does), is that your solution is attempting to work at a higher level of abstraction than cats.mtl does.
The problem that an average MTL implementation is usually trying to solve looks somewhat like this:
For a fixed property P and two fixed monads LameMonad and FancyMonad, find a way to lift P from the LameMonad to the FancyMonad.
This is done for a few useful properties P (such as that you can Ask, Tell, access and mutate Stateful stuff and so on), and a reasonable amount of different combinations of LameMonad and FancyMonad, with the fancy monads usually arising from the lame monads by applying some monad transformer (such as those from cats.data._). Note how the the quantifiers "for a few", "for a reasonable amount" appear in the metadiscussion outside of the problem statement that we're trying to solve automatically.
Now, contrast this to your code, where you greet the compiler with the following signature:
given monadTA[M[_]: Monad, T[_[_], _]: MonadTransformer] // ... etc
The contextual bound : MonadTransformer demands that the compiler solves a problem that looks roughly like
For a fixed T, find a unique constructive proof that
for all monads M, [X] => T[M, X] is also a monad.
Note how the for all quantifier has now slipped into the problem statement of the task we are trying to automate, and also note that now the compiler is somehow supposed to infer the "right" way to match a higher kind Foo against [A] =>> T[M, A] with a higher-kinded M.
The task of matching against [A] =>> T[M, A] is tricky (thanks to subclassing / inheritance even trickier than in Haskell), and actually somewhat ill-defined. For example, WriterT[IO, String, V] can be decomposed in multiple ways: is it
[X[_], Y] =>> WriterT[X, String, Y] applied to IO and V
or is it
[X[_], Y] =>> WriterT[IO, Y, X[V]] applied to Id[_] and String
or is it any other combination? Some conventions (taking the rightmost argument first etc.) seem to work in most common cases, but apparently not in your particular case.
So, without being able to tell for sure, I assume that all those universal quantifications over higher kinds somehow manage to confuse the compiler badly enough that the approach becomes impractical. I also assume that this is one of the reasons why cats.mtl is using MonadPartialOrder instead of MonadTransformer-typeclasses: the MonadPartialOrder[F, G] tells you just that you can do with G anything you can do with F, for two fixed monads F and G. The kinds of both parameters are * -> *, which is much more benign than all those higher-kinded [X[_], Y] =>> Z[X, Y]-lambdas.
So, to reiterate, MTL is doing this:
For a few selected `P`, `F`, `G`, solve problem: "lift P from F to G"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
meta-level, interpreted by humans easy for compiler
whereas you are attempting something closer to this (waves hands handwavily):
For a fixed `P`, solve: "for all `F`, `G`, lift `P` from `F` to `G`"
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
meta-level, easy too hard for the compiler
which is sufficient, but not necessary (and therefore unnecessarily hard for the compiler).

Related

Monad transformer for Future[Either[Error, Option[User]]]

Consider the signature of retrieveUser where retrieving a non-existent user is not modelled as an error, that is, it is modelled as Future[Right[None]]:
def retrieveUser(email: String): Future[Either[Error, Option[User]]]
Does there exist a monad transformer MT such that we can write
(for {
user <- MT(retrieveUser(oldEmail))
_ <- MT(updateUser(user.setEmail(newEmail)))
} {}).run
Using EitherT the best I can do is the following:
EitherT(retrieveUser(oldEmail)).flatMap {
case Some(user) =>
EitherT(updateUser(user.setEmail(newEmail)))
case None =>
EitherT.right(Future.successful({}))
}.run
The problem is that mapping over EitherT(retrieveUser(email)) results in Option[User], instead of unboxed User, which breaks the for-comprehension.
I assume that the order of parameters is the same as in EitherT form Scala Cats: an EitherT[F[_], A, B] is essentially just a wrapper around F[Either[A, B]].
Likewise, OptionT[F, A] is wrapper around F[Option[A]].
Thus,
OptionT[EitherT[Future, Error, ?], A]
is a wrapper around
EitherT[Future, Error, Option[A]]
which is in turn a wrapper around
Future[Either[Error, Option[A]]]
Therefore,
OptionT[EitherT[Future, Error, ?], User](
EitherT[Future, Error, Option[User]](retrieveUser(oldEmail))
)
should typecheck (with the non/kind-projector), and with -Ypartial-unification the types should also be inferred automatically, so you could try to use
OptionT(EitherT(retrieveUser(oldEmail))
inside the for-comprehensions.

Whether to use context bound or implicit ev in Scala

According to the style guide - is there a rule of thumb what one should use for typeclasses in Scala - context bound or implicit ev notation?
These two examples do the same
Context bound has more concise function signature, but requires val evaluation with implicitly call:
def empty[T: Monoid, M[_] : Monad]: M[T] = {
val M = implicitly[Monad[M]]
val T = implicitly[Monoid[T]]
M.point(T.zero)
}
The implicit ev approach automatically inserts typeclasses into function parameters but pollutes method signature:
def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] = {
M.point(T.zero)
}
Most of the libraries I've checked (e.g. "com.typesafe.play" %% "play-json" % "2.6.2") use implicit ev
What are you using and why?
This is very opinion-based, but one pratical reason for using an implicit parameter list directly is that you perform fewer implicit searches.
When you do
def empty[T: Monoid, M[_] : Monad]: M[T] = {
val M = implicitly[Monad[M]]
val T = implicitly[Monoid[T]]
M.point(T.zero)
}
this gets desugared by the compiler into
def empty[T, M[_]](implicit ev1: Monoid[T], ev2: Monad[M]): M[T] = {
val M = implicitly[Monad[M]]
val T = implicitly[Monoid[T]]
M.point(T.zero)
}
so now the implicitly method needs to do another implicit search to find ev1 and ev2 in scope.
It's very unlikely that this has a noticeable runtime overhead, but it may affect your compile time performance in some cases.
If instead you do
def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] =
M.point(T.zero)
you're directly accessing M and T from the first implicit search.
Also (and this is my personal opinion) I prefer the body to be shorter, at the price of some boilerplate in the signature.
Most libraries I know that make heavy use of implicit parameters use this style whenever they need to access the instance, so I guess I simply became more familiar with the notation.
Bonus, if you decide for the context bound anyway, it's usually a good idea to provide an apply method on the typeclass that searches for the implicit instance. This allows you to write
def empty[T: Monoid, M[_]: Monad]: M[T] = {
Monad[M].point(Monoid[T].zero)
}
More info on this technique here: https://blog.buildo.io/elegant-retrieval-of-type-class-instances-in-scala-32a524bbd0a7
One caveat you need to be aware of when working with implicitly is when using dependently typed functions. I'll quote from the book "The type astronauts guide to shapeless". It looks at the Last type class from Shapeless which retrieves the last type of an HList:
package shapeless.ops.hlist
trait Last[L <: HList] {
type Out
def apply(in: L): Out
}
And says:
The implicitly method from scala.Predef has this behaviour (this
behavior means losing the inner type member information). Compare the
type of an instance of Last summoned with implicitly:
implicitly[Last[String :: Int :: HNil]]
res6: shapeless.ops.hlist.Last[shapeless.::[String,shapeless
.::[Int,shapeless.HNil]]] = shapeless.ops.hlist$Last$$anon$34#20bd5df0
to the type of an instance summoned with Last.apply:
Last[String :: Int :: HNil]
res7: shapeless.ops.hlist.Last[shapeless.::[String,shapeless
.::[Int,shapeless.HNil]]]{type Out = Int} = shapeless.ops.hlist$Last$$anon$34#4ac2f6f
The type summoned by implicitly has no Out type member, that is an important caveat and generally why you would use the summoner pattern which doesn't use context bounds and implicitly.
Other than that, generally I find that it is a matter of style. Yes, implicitly might slightly increase compile times, but if you have an implicit rich application you'll most likely not "feel" the difference between the two at compile time.
And on a more personal note, sometimes writing implicitly[M[T]] feels "uglier" than making the method signature a bit longer, and might be clearer to the reader when you declare the implicit explicitly with a named field.
Note that on top of doing the same, your 2 examples are the same. Context bounds is just syntactic sugar for adding implicit parameters.
I am being opportunistic, using context bound as much as I can i.e., when I don't already have implicit function parameters. When I already have some, it is impossible to use context bound and I have no other choice but adding to the implicit parameter list.
Note that you don't need to define vals as you did, this works just fine (but I think you should go for what makes the code easier to read):
def empty[T: Monoid, M[_] : Monad]: M[T] = {
implicitly[Monad[M]].point(implicitly[Monoid[T]].zero)
}
FP libraries usually give you syntax extensions for typeclasses:
import scalaz._, Scalaz._
def empty[T: Monoid, M[_]: Monad]: M[T] = mzero[T].point[M]
I use this style as much as possible. This gives me syntax consistent with standard library methods and also lets me write for-comprehensions over generic Functors / Monads
If not possible, I use special apply on companion object:
import cats._, implicits._ // no mzero in cats
def empty[T: Monoid, M[_]: Monad]: M[T] = Monoid[T].empty.pure[M]
I use simulacrum to provide these for my own typeclasses.
I resort to implicit ev syntax for cases where context bound is not enough (e.g. multiple type parameters)

Does the order of implicit parameters matter in Scala?

Given some method
def f[A,B](p: A)(implicit a: X[A,B], b: Y[B])
Does the order of a before b within the implicit parameter list matter for type inference?
I thought only the placement of parameters within different parameter lists matters, e.g. type information flows only through parameter lists from left to right.
I'm asking because I noticed that changing the order of implicit parameters within the singly implicit list made a program of mine compile.
Real example
The following code is using:
shapeless 2.1.0
Scala 2.11.5
Here is a simple sbt build file to help along with compiling the examples:
scalaVersion := "2.11.5"
libraryDependencies += "com.chuusai" %% "shapeless" % "2.1.0"
scalaSource in Compile := baseDirectory.value
Onto the example. This code compiles:
import shapeless._
import shapeless.ops.hlist.Comapped
class Foo {
trait NN
trait Node[X] extends NN
object Computation {
def foo[LN <: HList, N <: HList, TupN <: Product, FunDT]
(dependencies: TupN)
(computation: FunDT)
(implicit tupToHlist: Generic.Aux[TupN, LN], unwrap: Comapped.Aux[LN, Node, N]) = ???
// (implicit unwrap: Comapped.Aux[LN, Node, N], tupToHlist: Generic.Aux[TupN, LN]) = ???
val ni: Node[Int] = ???
val ns: Node[String] = ???
val x = foo((ni,ns))((i: Int, s: String) => s + i.toString)
}
}
and this code fails
import shapeless._
import shapeless.ops.hlist.Comapped
class Foo {
trait NN
trait Node[X] extends NN
object Computation {
def foo[LN <: HList, N <: HList, TupN <: Product, FunDT]
(dependencies: TupN)
(computation: FunDT)
// (implicit tupToHlist: Generic.Aux[TupN, LN], unwrap: Comapped.Aux[LN, Node, N]) = ???
(implicit unwrap: Comapped.Aux[LN, Node, N], tupToHlist: Generic.Aux[TupN, LN]) = ???
val ni: Node[Int] = ???
val ns: Node[String] = ???
val x = foo((ni,ns))((i: Int, s: String) => s + i.toString)
}
}
with the following compile error
Error:(22, 25) ambiguous implicit values:
both method hnilComapped in object Comapped of type [F[_]]=> shapeless.ops.hlist.Comapped.Aux[shapeless.HNil,F,shapeless.HNil]
and method hlistComapped in object Comapped of type [H, T <: shapeless.HList, F[_]](implicit mt: shapeless.ops.hlist.Comapped[T,F])shapeless.ops.hlist.Comapped.Aux[shapeless.::[F[H],T],F,shapeless.::[H,mt.Out]]
match expected type shapeless.ops.hlist.Comapped.Aux[LN,Foo.this.Node,N]
val x = foo((ni,ns))((i: Int, s: String) => s + i.toString)
^
Error:(22, 25) could not find implicit value for parameter unwrap: shapeless.ops.hlist.Comapped.Aux[LN,Foo.this.Node,N]
val x = foo((ni,ns))((i: Int, s: String) => s + i.toString)
^
Error:(22, 25) not enough arguments for method foo: (implicit unwrap: shapeless.ops.hlist.Comapped.Aux[LN,Foo.this.Node,N], implicit tupToHlist: shapeless.Generic.Aux[(Foo.this.Node[Int], Foo.this.Node[String]),LN])Nothing.
Unspecified value parameters unwrap, tupToHlist.
val x = foo((ni,ns))((i: Int, s: String) => s + i.toString)
^
Normally it should not matter. If you look at the language spec it makes no mention about resolution being dependent on parameter order.
I looked at the source code of shapeless, and I could not come up with any reason why this error would present itself.
And doing a quick search through the bug repo of the language I found a similar issue that was apparently resolved. But it does not state if the fix involved treating the symptom (making context bounds not break compilation) or the cause (restrictions on implicit parameter ordering.)
Therefore I would argue that this is a compiler bug, and it is tightly related to the issue linked in point 3.
Also, I would suggest you submit a bug report if you can find a second opinion that resulted from a more rigorous analysis than my own :)
Hope this puts your mind at rest. Cheers!
According to my reading of the comments of the issue mentioned by Lorand Szakacs, I come to the conclusion that the order of implicit parameters matters in the current version 2.11 of the Scala compiler.
This is because the developers participating in the discussion appear to assume that the order matters; they do not state it explicitly.
I'm not aware of the language spec mentioning anything about this topic.
Reordering them will only break code that explicitly passes them, as well as all compiled code. Everything else will be unaffected.

Understanding TraversableOnce and Subtyping

I am trying to implement a distinctBy method, I can do it easily enough for Seq, but being an adventurous fellow, I wanted to try doing something a little more generic.
def distinctBy[A, B, M[_] <: TraversableOnce[_]](xs: M[A])(f: A => B)(implicit cbf: CanBuildFrom[M[A], A, M[A]]): M[A] = {
val seen = mutable.Set.empty[B]
val builder = cbf(xs)
for (x <- xs) {
val k = f(x)
if (!seen.contains(k)) {
seen += k
builder += x
}
}
builder.result()
}
The basic intuition here is that I map to a type and keep track of a set of those, rather than the equality defined on the elements of the original collection. So i could take the distinct set of tuples comparing just the left element, or the right.
I cannot get this to compile, even if I switch to using just forall, because it doesn't seem to think that xs has any of the methods defined on TraversableOnce, even though there is a subtyping relation. I am obviously missing something quite elementary here, and would be grateful for any help rendered.
You probably want the constraint to be M[X] <: TraversableOnce[X]. _ is special, and with your current signature you will only know that M[A] <: TraversableOnce[_], whereas you want M[A] <: TraversableOnce[A].
You're missing a closing brace, which might be important.
What's the exact compilation error you're getting?

Why isn't Validation a Monad?

an example use case:
def div2(i: Int): Validation[String, Int] =
if (i%2 == 0) Validation.success(i/2)
else Validation.failure("odd")
def div4(i: Int) = for {
a <- div2(i)
b <- div2(a)
} yield b
error: Unable to unapply type scalaz.Validation[String,Int] into a type constructor of kind M[_] that is classified by the type class scalaz.Bind
I guess the error is caused by the compiler can't find a Monad instance for Validation[String, Int]
I can make one for myself, like:
object Instances {
implicit def validationMonad[E] = new Monad[({type L[A] = Validation[E, A]})#L] {
override def point[A](a: => A) =
Validation.success(a)
override def bind[A, B](fa: Validation[E, A])(f: A => Validation[E, B]) =
fa bind f
}
}
but why doesn't Validation have it already? after all, Validation already has the bind method defined.
moreover, I can't have import Validation._ and import Instances._ together anymore (this took me looong to figure out...), because of another complicated error...
ambiguous implicit values: something like both validationMonad (my instance), and method ValidationInstances1 in trait ValidationInstances2... both match some Functor of Validation...
should I modify the source of scalaz? or I'm completely missing something~?
please help~
I'm using scalaz 7.0.0-M2
As discussed in the Scalaz group, the problem seems to be that ap would accumulate errors whereas (pseudo-)monadic composition would only operate on the value part of Validation.
Therefore, one cannot be expressed in terms of the other and thus no monad instance exists for Validation.
The issue is that the applicative functor as implied by the monad does not equal the actual applicative functor