Ambiguous implicit values for Typeclass

1.9k Views Asked by At

I am trying to abstract out the json parsing logic that gets triggered for a specific type.

I started out creating a Parser trait as follows:

trait Parser {
  def parse[T](payload : String) : Try[T]
}

I have an implementation of this trait called JsonParser which is:

class JsonParser extends Parser {

  override def parse[T](payload: String): Try[T] = parseInternal(payload)

  private def parseInternal[T:JsonParserLike](payload:String):Try[T] = {
    implicitly[JsonParserLike[T]].parse(payload)
  }
} 

The JsonParserLike is defined as follows:

trait JsonParserLike[T] {
  def parse(payload: String): Try[T]
}

object JsonParserLike {
  implicit val type1Parser:JsonParserLike[Type1] = new JsonParserLike[Type1] 
  {
    //json parsing logic for Type1
  }

  implicit val type2Parser:JsonParserLike[Type2] = new JsonParserLike[Type2]
  {
     //json parsing logic for Type2
  }
}

When I try compiling the above, the compilation fails with:

ambiguous implicit values:
[error]  both value type1Parse in object JsonParserLike of type => parser.jsonutil.JsonParserLike[parser.models.Type1]
[error]  and value type2Parser in object JsonParserLike of type => parser.jsonutil.JsonParserLike[parser.models.Type2]
[error]  match expected type parser.jsonutil.JsonParserLike[T]
[error]   override def parse[T](payload: String): Try[T] = parseInternal(payload)

Not sure why the implicit resolution is failing here. Is it because the parse method in the Parser trait doesn't have an argument of type parameter T?

I tried another approach as follows:

trait Parser {
  def parse[T](payload : String) : Try[T]
}

class JsonParser extends Parser {

  override def parse[T](payload: String): Try[T] = {
    import workflow.parser.JsonParserLike._
    parseInternal[T](payload)
  }

  private def parseInternal[U](payload:String)(implicit c:JsonParserLike[U]):Try[U] = {
     c.parse(payload)
  }
}

The above gives me the following error:

could not find implicit value for parameter c: parser.JsonParserLike[T]
[error]     parseInternal[T](payload)
[error]  

               ^

Edit: Adding the session from the REPL

scala> case class Type1(name: String)
defined class Type1

scala> case class Type2(name:String)
defined class Type2

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.util.{Failure, Success, Try}

trait JsonParserLike[+T] {
  def parse(payload: String): Try[T]
}

object JsonParserLike {

  implicit val type1Parser:JsonParserLike[Type1] = new JsonParserLike[Type1] {

    override def parse(payload: String): Try[Type1] = Success(Type1("type1"))
  }

  implicit val type2Parser:JsonParserLike[Type2] = new JsonParserLike[Type2] {

    override def parse(payload: String): Try[Type2] = Success(Type2("type2"))
  }
}

// Exiting paste mode, now interpreting.

import scala.util.{Failure, Success, Try}
defined trait JsonParserLike
defined object JsonParserLike

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Parser {
  def parse[T](payload : String) : Try[T]
}

class JsonParser extends Parser {

  override def parse[T](payload: String): Try[T] = parseInternal(payload)

  private def parseInternal[T:JsonParserLike](payload:String):Try[T] = {
    implicitly[JsonParserLike[T]].parse(payload)
  }
}

// Exiting paste mode, now interpreting.

<pastie>:24: error: ambiguous implicit values:
 both value type1Parser in object JsonParserLike of type => JsonParserLike[Type1]
 and value type2Parser in object JsonParserLike of type => JsonParserLike[Type2]
 match expected type JsonParserLike[T]
  override def parse[T](payload: String): Try[T] = parseInternal(payload)
2

There are 2 best solutions below

0
On BEST ANSWER

It seems like there is an extra complexity from a mix of different types of polymorphism in both examples. Here is a minimal example of just a type class:

// type class itself
trait JsonParser[T] {
  def parse(payload: String): Try[T]
}

// type class instances
object JsonParserInstances {
  implicit val type1Parser: JsonParser[Type1] = new JsonParser[Type1] {
    def parse(payload: String): Try[Type1] = ???
  }

  implicit val type2Parser: JsonParser[Type2] = new JsonParser[Type2] {
    def parse(payload: String): Try[Type2] = ???
  }
}

// type class interface
object JsonInterface {
  def parse[T](payload: String)(implicit T: JsonParser[T]): Try[T] = {
    T.parse(payload)
  }
}

def main(args: Array[String]): Unit = {
  import JsonParserInstances._
  JsonInterface.parse[Type1]("3")
  JsonInterface.parse[Type2]("3")
}

More info:

3
On

As I've already tried to explain in the comments, the problem is that the method

override def parse[T](payload: String): Try[T] = parseInternal(payload)

does not accept any JsonParserLike[T] instances. Therefore, the compiler has no way to insert the right instance of JsonParserLike[T] at the call site (where the type T is known).

To make it work, one would have to add some kind of token that uniquely identifies type T to the argument list of parse. One crude way would be to add a JsonParserLike[T] itself:

import util.Try

trait Parser {
  def parse[T: JsonParserLike](payload : String) : Try[T]
}

class JsonParser extends Parser {

  override def parse[T: JsonParserLike](payload: String): Try[T] = 
    parseInternal(payload)

  private def parseInternal[T:JsonParserLike](payload:String):Try[T] = {
    implicitly[JsonParserLike[T]].parse(payload)
  }
} 

trait JsonParserLike[T] {
  def parse(payload: String): Try[T]
}

object JsonParserLike {
  implicit val type1Parser: JsonParserLike[String] = ???
  implicit val type2Parser: JsonParserLike[Int] = ???
}

Now it compiles, because the JsonParserLike[T] required by parseInternal is inserted automatically as an implicit parameter to parse.

This might be not exactly what you want, because it creates a hard dependency between Parser interface and the JsonParserLike typeclass. You might want to get some inspiration from something like shapeless.Typeable to get rid of the JsonParserLike in the Parser interface, or just rely on circe right away.