2
votes

Here is an example that works fine:

  import cats.data.ReaderT
  import cats.instances.option._
  ...
  def f1:ReaderT[Option, Service, Int] =
    ReaderT(service => for {
      res <- Some(10)
    } yield res )

Here is an example that is not compiled:

  def f2:ReaderT[Option, Service, Int] =
    for {
      res <- ReaderT((_:Service) => Some(10))
    } yield res

I get the following error:

Error:(53, 11) could not find implicit value for parameter F: cats.Functor[Some] res <- ReaderT((:Service) => Some(10)) Error:(53, 11) not enough arguments for method map: (implicit F: cats.Functor[Some])cats.data.Kleisli[Some,com.savdev.Service,Int]. Unspecified value parameter F. res <- ReaderT((:Service) => Some(10))

To fix the error in the 2nd example, I must return not Some, but Option, which is a parent for Some:

  def f2:ReaderT[Option, Service, Int] =
    for {
      res <- ReaderT((_:Service) => Option(10))
    } yield res

Can you please explain this? Why in the 1st example returning Some, but not Option, works fine. Why at the same time in the 2nd example, returning Some is not compiled? Is there an option for Scala compiler to compile cases, like in the 2nd example? Or other solution.

2

2 Answers

4
votes
  1. In the first case, it invokes map directly on Some, which is known to return an Option (ordinary polymorphism with subclass Some of Option), then it goes on to find Functor[Option].

  2. In the second case, the return type of your function is inferred to be Some[Int], and the compiler attempts to find an instance of Functor[Some] typeclass in order to invoke a method on Reader (ad-hoc polymorphism with typeclass Functor), but this fails, because there is no functor for Some.

The main problem is that Some isn't just a constructor of instances of type Option (as it would be in Haskell, for example), but it is actually a constructor of instances of (mostly useless) type Some, which sometimes messes up type inference / implicits resolution.

If you want to enforce that the resulting type is Option[Int], use Option(10) to construct Some or Option.empty to construct None.

3
votes

Try

import cats.syntax.option._

def f2: ReaderT[Option, Service, Int] =
  for {
    res <- ReaderT((_: Service) => 10.some)
  } yield res

It's better to use x.some and none (or none[X]) instead of Some(x) and None. They have type Option[X] instead of Some[X] and None.type. This can improve type inference sometimes. Actually it's Option that is an instance of Functor and not Some.

Look at "Extension method constructors" in https://blog.softwaremill.com/9-tips-about-using-cats-in-scala-you-might-want-to-know-e1bafd365f88

In the first case you were just lucky that types were inferred properly.