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

Учитывая кортеж с элементами типа 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 работает, заключается в том, что функция literal:

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 имеют одинаковые type, потому что они имеют одинаковый тип зависимого от пути.