Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala cats ambiguous implicit values

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))
                                                                        ^
like image 875
Knows Not Much Avatar asked Feb 04 '19 05:02

Knows Not Much


1 Answers

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.

like image 78
Travis Brown Avatar answered Nov 07 '22 11:11

Travis Brown