Странное несоответствие типов при использовании доступа к элементу вместо экстрактора
Дан кортеж с элементами типа 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
, который является полиморфным, должен быть сделан мономорфным по двум причинам (которые я знаю):
Функции в Scala не могут быть полиморфными; только методы могут быть.
process
как определено как метод; когда используется как первоклассная функция, это функция.Компилятор делает вывод типов главным образом, принимая полиморфные значения и объединяя их вместе, чтобы сделать их менее полиморфными. Передача фактического полиморфного значения в качестве аргумента метода потребует типов более высокого ранга.
Причина того, что 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
имеют один и тот же параметр типа, потому что они имеют один и тот же тип, зависящий от пути.