vasigorc
vasigorc

Reputation: 972

Scala: `ambigious implicit values` but the right value is not event found

I am writing a small Scala Program which should:

  1. Read a file (line by line) from a local FS
  2. Parse from each line three double values
  3. Make instances of a case class based on those three values
  4. Pass those instances to a Binary Heap

To be able to parse Strings to both Doubles and CoordinatePoints I've came up with this trait:

trait Parseable[T] {
  def parse(input: String): Either[String, T]
}

and I have a number of type object implementations for the latter:

object Parseable {
  implicit val parseDouble: Parseable[Double] = new Parseable[Double] {
    override def parse(input: String): Either[String, Double] = {
      val simplifiedInput = input.replaceAll("[ \\n]", "").toLowerCase
      try Right(simplifiedInput.toDouble) catch {
        case _: NumberFormatException =>
          Left(input)
      }
    }
  }

  implicit val parseInt: Parseable[Int] = new Parseable[Int] {
    override def parse(input: String): Either[String, Int] = {
      val simplifiedInput = input.replaceAll("[ \\n]", "").toLowerCase
      try Right(simplifiedInput.toInt) catch {
        case _: NumberFormatException =>
          Left(input)
      }
    }
  }

  implicit val parseCoordinatePoint: Parseable[CoordinatePoint] = new Parseable[CoordinatePoint] {
    override def parse(input: String): Either[String, CoordinatePoint] = {
      val simplifiedInput = input.replaceAll("[ \\n]", "").toLowerCase
      val unparsedPoints: List[String] = simplifiedInput.split(",").toList
      val eithers: List[Either[String, Double]] = unparsedPoints.map(parseDouble.parse)
      val sequence: Either[String, List[Double]] = eithers.sequence
      sequence match {
        case Left(value) => Left(value)
        case Right(doublePoints) => Right(CoordinatePoint(doublePoints.head, doublePoints(1), doublePoints(2)))
      }
    }
  }
}

I have a common object that delegates the call to a corresponding implicit Parseable (in the same file):

object InputParser {
  def parse[T](input: String)(implicit p: Parseable[T]): Either[String, T] = p.parse(input)
}

and just for reference - this is the CoordinatePoint case class:

case class CoordinatePoint(x: Double, y: Double, z: Double)

In my main program (after having validated that the file is there, and is not empty, etc..) I want to transform each line into an instance of CoordinatePoint as follows:

  import Parseable._
  import CoordinatePoint._

  ...
  private val bufferedReader = new BufferedReader(new FileReader(fileName))

  private val streamOfMaybeCoordinatePoints: Stream[Either[String, CoordinatePoint]] = Stream
    .continually(bufferedReader.readLine())
    .takeWhile(_ != null)
    .map(InputParser.parse(_))

and the error I get is this:

[error] /home/vgorcinschi/data/eclipseProjects/Algorithms/Chapter 2 Sorting/algorithms2_1/src/main/scala/ca/vgorcinschi/algorithms2_4/selectionfilter/SelectionFilter.scala:42:27: ambiguous implicit values:
[error]  both value parseDouble in object Parseable of type => ca.vgorcinschi.algorithms2_4.selectionfilter.Parseable[Double]
[error]  and value parseInt in object Parseable of type => ca.vgorcinschi.algorithms2_4.selectionfilter.Parseable[Int]
[error]  match expected type ca.vgorcinschi.algorithms2_4.selectionfilter.Parseable[T]
[error]     .map(InputParser.parse(_))
[error]                           ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 1 s, completed Sep 1, 2020 10:38:18 PM

I don't understand nor know where to look for why is the compiler finding Parseable[Int] and Parseable[Double] but not the only right one - Parseable[CoordinatePoint].

So I thought, ok let me give the compiler a hand by specifying the transformation function from beforehand:

  private val bufferedReader = new BufferedReader(new FileReader(fileName))

  val stringTransformer: String => Either[String, CoordinatePoint] = s => InputParser.parse(s)

  private val streamOfMaybeCoordinatePoints: Stream[Either[String, CoordinatePoint]] = Stream
    .continually(bufferedReader.readLine())
    .takeWhile(_ != null)
    .map(stringTransformer)

Alas this yields the same error just a bit up the code - in the function declaration.

I would love to learn what is that that causes such behavior. Both to rectify the code and for personal knowledge. At this point I am very curious.

Upvotes: 0

Views: 160

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51723

One fix is to specify type prameter explicitly

InputParser.parse[CoordinatePoint](_)

Another is to prioritize implicits. For example

trait LowPriorityParseable1 {
  implicit val parseInt: Parseable[Int] = ...
}

trait LowPriorityParseable extends LowPriorityParseable1 {
  implicit val parseDouble: Parseable[Double] = ...
}

object Parseable extends LowPriorityParseable {
  implicit val parseCoordinatePoint: Parseable[CoordinatePoint] = ...
}

By the way, since you put implicits into the companion object it doesn't make much sense now to import them.

In the call site of

object InputParser {
  def parse[T](input: String)(implicit p: Parseable[T]): Either[String, T] = p.parse(input)
}

type parameter T is inferred (if not specified explicitly) not before the implicit is resolved (type inference and implicit resolution make impact on each other). Otherwise the following code wouldn't compile

trait TC[A]
object TC {
  implicit val theOnlyImplicit: TC[Int] = null
}    
def materializeTC[A]()(implicit tc: TC[A]): TC[A] = tc
  
materializeTC() // compiles, A is inferred as Int

So during implicit resolution compiler tries to infer types not too early (otherwise in the example with TC type A would be inferred as Nothing and implicit wouldn't be found). By the way, an exception is implicit conversions where compiler tries to infer types eagerly (sometimes this can make troubles too)

// try to infer implicit parameters immediately in order to:
//   1) guide type inference for implicit views
//   2) discard ineligible views right away instead of risking spurious ambiguous implicits

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Implicits.scala#L842-L854

Upvotes: 3

Artem Sokolov
Artem Sokolov

Reputation: 850

The problem that the compiler does not inference and fix type parameter T in .map(InputParser.parse(_)) before trying to find the implicit in the second parameter list.

In the compiler, there is a concrete algorithm that infers types with its own logic, constraints, and tradeoffs. In that concrete compiler version that you use it first goes to the parameter lists and infer and checks types list by list, and only at the end, it infers type parameter by returning type (I do not imply that in other versions it differs, I only point out that it is implementation behavior not a fundamental constraint).

More precisely what is going on is that type parameter T is not being inferred or specified somehow at the step of typechecking of the second parameter list. T (at that point) is existential and it can be any/every type and there is 3 different implicit object that suitable for such type.

It is just how the compiler and its type inference works for now.

Upvotes: 1

Related Questions