Странное несоответствие типов при использовании доступа к элементу вместо экстрактора

Дан кортеж с элементами типа A и другой тип параметризован в A:

trait Writer[-A] { def write(a: A): Unit }
case class Write[A](value: A, writer: Writer[A])

И сайт использования:

trait Cache { def store[A](value: A, writer: Writer[A]): Unit }

Почему следующее работает, как и ожидалось, используя экстрактор кортежа:

def test1(set: Set[Write[_]], cache: Cache): Unit =
  set.foreach {
    case Write(value, writer) => cache.store(value, writer)
  }

Но следующее терпит неудачу:

def test2(set: Set[Write[_]], cache: Cache ): Unit =
  set.foreach { write =>
    cache.store(write.value, write.writer)
  }

с сообщением об ошибке

 found   : Writer[_$1] where type _$1
 required: Writer[Any]
             cache.store(write.value, write.writer)
                                        ^

Можно ли исправить вторую форму (test2) правильно скомпилировать?

РЕДАКТИРОВАТЬ

Исходя из идей Оуэна, я попробовал, смогу ли я заставить это работать без сопоставления с образцом вообще (что я и хотел в первую очередь). Вот еще два странных случая, один работает, другой нет:

// does not work
def test3(set: Set[Write[_]], cache: Cache): Unit = {
  def process[A](write: Write[A]): Unit =
    cache.store(write.value, write.writer)

  set.foreach(process)
}

// _does work_
def test4(set: Set[Write[_]], cache: Cache): Unit = {
  def process[A](write: Write[A]): Unit =
    cache.store(write.value, write.writer)

  set.foreach(w => process(w))
}

Все еще довольно неясен для меня...

1 ответ

Решение

Бег с -Xprint:typer освещает здесь Проблема с test2 является то, что существует экзистенциальный тип, который появляется в двух отдельных местах: оба write.value а также write.writer имеют экзистенциальный тип, но, что особенно важно, компилятор не может знать, что они имеют одинаковую экзистенциально-количественную переменную типа. Даже если вы обращаетесь к ним из одного и того же объекта, компилятор забывает, что они пришли из одного и того же места.

когда test1 полностью напечатан, вы видите:

def test1(set: Set[Write[_]], cache: Cache) =
    set.foreach(((x0$1: Write[_]) => x0$1 match {
      case (value: _$1, writer: Writer[_$1])Write[_$1]((value @ _), (writer @ _)) =>
          cache.store[_$1](value, writer)
    }));

Переменная типа _$1 сопоставляется со значениями. Соответствие переменной типа _$1 связывает его в рамках case так что это уже не экзистенциально, и Scala может сказать, что value а также writer имеют один и тот же параметр типа.

Решение для test2 это не использовать экзистенции:

def test2[A]( set: Set[ Write[ A ]], cache: Cache ) {
   set.foreach { write =>
      cache.store( write.value, write.writer )
   }
}

или связать переменную типа с совпадением:

def test2( set: Set[ Write[ _ ]], cache: Cache ) {
   set.foreach { case write: Write[a] =>
      cache.store( write.value, write.writer )
   }
}

редактировать

Позвольте мне постараться ответить на новые вопросы, которые вы подняли.

Причина test3 не работает, это когда пишешь

set.foreach(процесс)

process, который является полиморфным, должен быть сделан мономорфным по двум причинам (которые я знаю):

  1. Функции в Scala не могут быть полиморфными; только методы могут быть. process как определено как метод; когда используется как первоклассная функция, это функция.

  2. Компилятор делает вывод типов главным образом, принимая полиморфные значения и объединяя их вместе, чтобы сделать их менее полиморфными. Передача фактического полиморфного значения в качестве аргумента метода потребует типов более высокого ранга.

Причина того, что test4 работает то, что функция литерал:

set.foreach( w => process( w ))

на самом деле не полиморфная функция! В качестве аргумента он принимает экстенсивно определенный тип; но не полиморфный тип. Затем он вызывает метод (а не функцию) process и соответствует переменной экзистенциального типа process Тип параметра. Довольно дикий, а?

Вы также можете написать:

set.foreach( process(_) )

что, создавая анонимную функцию, означает то же самое.

Другой путь, который вы можете или не можете найти подходящим, будет отбрасывать экзистенциальные типы и использовать члены типов:

trait Writable {
    type A
    val value: A
    val writer: Writer[A]
}

case class Write[T]( value: T, writer: Writer[ T ]) extends Writable {
    type A = T
}

def test2( set: Set[Writable], cache: Cache ) {
    set.foreach { write =>
        cache.store( write.value, write.writer )
    }
}

Здесь Scala может видеть, что write.value а также write.writer имеют один и тот же параметр типа, потому что они имеют один и тот же тип, зависящий от пути.

Другие вопросы по тегам