Search code examples
scalatype-inferenceimplicitreader-monad

Scala cannot infer parameter type in Reader monad implementation


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.


Solution

  • 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
      }
    }