1

I am using Scala 2.13, and I am developing the Reader monad my own. The implementation of the monad is the following.

object ReaderMonad {

  implicit def reader[From, To](f: From => To): Reader[From, To] = Reader(f)

  case class Reader[From, To](f: From => To) {
    def run(input: From): To =
      f(input)

    def map[NewTo](transformation: To => NewTo): Reader[From, NewTo] =
      Reader(c => transformation(f(c)))

    def flatMap[NewTo](transformation: To => Reader[From, NewTo]): Reader[From, NewTo] =
      Reader(c => transformation(f(c)).run(c))
  }

  def pure[From, To](a: To): Reader[From, To] = Reader((c: From) => a)
}

Using such a monad, I am defining a repository for stocks.

trait StockRepository {
    def findAll: Map[String, Double]
}

Stocks service uses the implementation of the repository, using the Reader monad to inject the repo dependency.

object Stocks {
  def findAll: Reader[StockRepository, Map[String, Double]] = 
    (repo: StockRepository) => repo.findAll()
}

My question is, why have I to specify the repo parameter type explicitly in the last function definition, (repo: StockRepository) => repo.findAll()? Why cannot the Scala compiler infer the type implicitly for me?

Thanks a lot.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
riccardo.cardin
  • 7,971
  • 5
  • 57
  • 106
  • Instead of relaying in an implicit conversion, I would use an explicit `apply`. Look into the **cats** one. – Luis Miguel Mejía Suárez Feb 07 '20 at 20:46
  • Thanks, but your comment does not answer my question :P – riccardo.cardin Feb 07 '20 at 20:47
  • 1
    OK so, the compiler doesn't have any way to infer the type of the input. It just sees a function which doesn't match the expected return type so it doesn't have any way to infer. For you it is obvious, because you want the implicit conversion to be applied. But the compiler can not start assuming so many things. And that is one of the reasons the implicit conversion is a bad practice, if you want it to happen then it would be better explicit. So my suggestion using an `apply` method would help, because then the compiler would be able to infer the input type. – Luis Miguel Mejía Suárez Feb 07 '20 at 21:53
  • Sorry, I didn't understand your first comment :( But I still cannot understand how adding an `apply` method to the object `Reader` can solve my problem. Why don't you post an answer so I can accept it? Maybe, you can add an example :) – riccardo.cardin Feb 08 '20 at 09:57

1 Answers1

2

Sure, you only need to remove the implicit conversion and add the apply.
However, I added some other changes to make the code more idiomatic and easier to use, if you have any doubt just let me know.

object ReaderMonad {
  final case class Reader[From, To] private[ReaderMonad] (run: From => To) {
    def map[NewTo](transformation: To => NewTo): Reader[From, NewTo] =
      Reader(c => transformation(run(c)))

    def flatMap[NewTo](transformation: To => Reader[From, NewTo]): Reader[From, NewTo] =
      Reader(c => transformation(run(c)).run(c))
  }

  // This is all you really need.
  def apply[From, To](f: From => To): Reader[From, To] =
    Reader(run = f)

  // This is called the parially applied trick:
  // https://typelevel.org/cats/guidelines.html
  // It makes easier to use pure.
  private[ReaderMonad] final class PurePartiallyApplied[From](private val dummy: Boolean) extends AnyVal {
    @inline
    final def apply[To](a: To): Reader[From, To] =
      Reader(_ => a)
  }

  // So now, you can just:
  // ReaderMonad.pure[String](10)
  // Instead of:
  // ReaderMonad.pure[String, Int](10)
  def pure[From]: PurePartiallyApplied[From] =
    new PurePartiallyApplied(dummy = true)
}

And you can use it like this:

trait StockRepository {
  def findAll: Map[String, Double]
}

object Stocks {
  import ReaderMonad.Reader

  def findAll: Reader[StockRepository, Map[String, Double]] =
   ReaderMonad { repo =>
     repo.findAll
  }
}