Search code examples
scalatypestype-inferencescala-2.10existential-type

Scala type inference on an existential type


Consider the following code snippet, which is a reduced version of my original problem:

case class RandomVariable[A](values: List[A])
case class Assignment[A](variable: RandomVariable[A], value: A)

def enumerateAll(vars: List[RandomVariable[_]], evidence: List[Assignment[_]]): Double = 
  vars match {
    case variable :: tail =>
      val enumerated = for {value <- variable.values
        extendedEvidence = evidence :+ Assignment(variable, value)
      } yield enumerateAll(tail, extendedEvidence)
      enumerated.sum
    case Nil => 1.0
  }

This fails with the compile-time error that variable was inferred to have type RandomVariable[_0] when Assignment required type Any. Why is value not also inferred to have type _0? I tried giving the existential type a name in order to give a hint to the compiler by using case (variable: RandomVariable[T forSome {type T}]) :: tail => but that also would not compile (saying it could not find type T, which I'd be interested in an explanation for as well).

For further motivation, consider when we capture the type parameter as follows:

case variable :: tail =>
  def sum[A](variable: RandomVariable[A]): Double = {
    val enumerated = for {value <- variable.values
      extendedEvidence = evidence :+ Assignment(variable, value)
      } yield enumerateAll(tail, extendedEvidence)
    enumerated.sum
  }
  sum(variable)

This compiles without warnings/errors. Is there something I can modify in the first example to not require this extra function?

EDIT: To be more explicit, I want to know why value is not inferred to be of type _0 even though variable is of type _0 and every value comes from a List[_0] in variable. Also I would like to know if there are any additional ways to tell the compiler of this fact (aside from capturing the type in a function as I gave above).


Solution

  • Another compiling solution, that is cleaner(?) than using a function to capture the type. However, it makes it even more puzzling as to why type inference fails in the original case.

    def enumerateAll(vars: List[RandomVariable[_]], evidence: List[SingleAssignment[_]]): Double = vars match {
      case (variable@RandomVariable(values)) :: tail =>
        val enumeration = for {value <- values
          assignment = SingleAssignment(variable, value)
          extendedEvidence = evidence :+ assignment
        } yield enumerateAll(tail, extendedEvidence)
        enumeration.sum
      case Nil => 1.0
    }
    

    It also returns the following warning:

    scala: match may not be exhaustive.
    It would fail on the following input: List((x: questions.RandomVariable[?] forSome x not in questions.RandomVariable[?]))
      def enumerateAll(vars: List[RandomVariable[_]], evidence: List[SingleAssignment[_]]): Double = vars match {
    

    Which I'm unable to decipher as of this posting. Also, running it with a few test cases produces the desired result without a match error using RandomVariables of int, double, and string in the parameter list.