Scala вывод типа для экзистенциального типа

Рассмотрим следующий фрагмент кода, который является уменьшенной версией моей исходной проблемы:

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
  }

Это не выполняется с ошибкой времени компиляции, в которой variable было установлено, что имеет тип RandomVariable[_0], когда Assignment требуется тип Any. Почему value также не предполагается, что у вас есть тип _0 ? Я попытался присвоить имя экзистенциального типа, чтобы дать подсказку для компилятора с помощью case (variable: RandomVariable[T forSome {type T}]) :: tail =>, но это также не будет компилироваться (говоря, что он не мог найти тип T, о котором мне также было бы интересно объяснение).

Для дальнейшей мотивации рассмотрим, когда мы фиксируем параметр типа следующим образом:

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)

Это компилируется без предупреждений/ошибок. Есть ли что-то, что я могу изменить в первом примере, чтобы не требовать этой дополнительной функции?

EDIT. Чтобы быть более явным, я хочу знать, почему value не выводится типа _0, хотя variable имеет тип _0, и каждое значение приходит от a List[_0] в variable. Кроме того, я хотел бы знать, есть ли какие-либо дополнительные способы сообщить компилятору об этом факте (кроме того, что он записывает тип в функции, как я уже говорил выше).

Ответ 1

Другое компилируемое решение, более чистое (?), чем использование функции для захвата типа. Тем не менее, это еще более озадачивает вопрос о том, почему вывод типа не выполняется в исходном случае.

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

Он также возвращает следующее предупреждение:

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 {

Который я не могу расшифровать с этой публикации. Кроме того, запуск его с помощью нескольких тестовых примеров дает желаемый результат без ошибки совпадения с использованием RandomVariable для int, double и string в списке параметров.

Ответ 2

Нельзя ли связать типы RandomVariable и Assignment вместе?

 def [A] enumerateAll(vars: List[RandomVariable[A]], evidence: List[Assignment[A]]): Double = 

на самом деле, вы можете быть более разрешительным и просто сказать

 def [A] enumerateAll(vars: List[RandomVariable[A]], evidence: List[Assignment[_ <: A]]): Double = 

Ответ 3

Код ошибки дает некоторое представление о решении.

<console>:15: error: type mismatch;
 found   : RandomVariable[_0] where type _0
 required: RandomVariable[Any]
Note: _0 <: Any, but class RandomVariable is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
           extendedEvidence = evidence :+ Assignment(variable, value)

Это говорит вам, что он видел более специфический тип, чем он предполагал, и даже предложил сделать RandomVariable допустимым ковариант A. Это позволило бы ему изменять тип вниз, когда это необходимо.

case class RandomVariable[+A](values: List[A])

В качестве альтернативы вы можете явно задать общий тип в enumerateAll для обоих параметров. Таким образом, он может вывести соответствующий тип вместо принудительного вывода Any. Это определение не требует случайного переменного ковариантного изменения, так как оба параметра одного типа.

def enumerateAll[A](vars: List[RandomVariable[A]], evidence: List[Assignment[A]]): Double = 

Этот вопрос может помочь в объяснении. Почему пример не компилируется, а как работает (co-, contra- и in) дисперсия?