Странное несоответствие типов при использовании доступа к элементу вместо экстрактора - PullRequest
6 голосов
/ 08 марта 2012

При наличии кортежа с элементами типа 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 Ответ

7 голосов
/ 08 марта 2012

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

...