12
import cats._
import cats.implicits._

trait Console[F[_]]{
  def readInput() : F[Int]
  def print(msg: String) : F[Unit]
} 

class Foo {
  def doFoo[F[_]: Monad](number: Int)(implicit C: Console[F]) : F[Unit] = {
    C.readInput().flatMap{input => 
      if (input == number) C.print("you won").map(_ => ())
      else if (input > number) C.print("you guessed too high").flatMap(_ => doFoo(number))
      else C.print("you guessed too low").flatMap(_ => doFoo(number))
    }
  }
} 

But I get this cryptic error from the compiler

cmd18.sc:5: ambiguous implicit values:
 both value catsStdInstancesForList in trait ListInstances of type => cats.Traverse[List] with cats.Alternative[List] with cats.Monad[List] with cats.CoflatMap[List]
 and value catsStdInstancesForVector in trait VectorInstances of type => cats.Traverse[Vector] with cats.Monad[Vector] with cats.Alternative[Vector] with cats.CoflatMap[Vector]
 match expected type cats.Monad[F]
else if (input > number) C.print("you guessed too high").flatMap(_ => dooFoo(number))
                                                                        ^
Knows Not Much
  • 30,395
  • 60
  • 197
  • 373

1 Answers1

21

The problem is that you're asking too much of Scala's type inference. It's trying to figure out the type parameter it needs for doFoo[?](number), and while it's pretty clear to us as humans that it has to be F given the context the expression doFoo(number) appears in, the compiler isn't that smart.

The simplest solution is just to provide the type parameter explicitly:

.flatMap(_ => doFoo[F](number))

If you find that annoying, you can help the compiler out a bit by desugaring the F[_]: Monad constraint bound so that you can make the order of the Console and Monad instances explicit:

import cats._
import cats.implicits._

trait Console[F[_]]{
  def readInput() : F[Int]
  def print(msg: String) : F[Unit]
} 

class Foo {
  def doFoo[F[_]](number: Int)(implicit C: Console[F], F: Monad[F]) : F[Unit] = {
    C.readInput().flatMap{input => 
      if (input == number) C.print("you won").map(_ => ())
      else if (input > number) C.print("you guessed too high").flatMap(_ => doFoo(number))
      else C.print("you guessed too low").flatMap(_ => doFoo(number))
    }
  }
}

In your original version, the Monad context bound was getting desugared to an implicit Monad[F] parameter that came before C: Console[F] in the implicit parameter list, and so the compiler was trying to resolve it first. In the sugar-free version above I've reversed the order so that it will resolve the Console[F] first, which will make everything work out just fine when the compiler gets around to trying to infer F for the doFoo(number) calls.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 1
    Thanks a ton. I was also able to do `def play[F[_]: Random: Console: Monad]() : F[Unit] = {` which compiled fine without explicitly specifying F when calling doFoo. – Knows Not Much Feb 04 '19 at 21:49
  • 2
    Yep, that desugars to the same thing. My own preference would be to write out the arguments when the order matters, but that's a matter of taste. – Travis Brown Feb 05 '19 at 07:55