Вывод типа завершается неудачно на Set, сделанном с помощью .toSet? - PullRequest
26 голосов
/ 05 апреля 2011

Почему вывод типа здесь терпит неудачу?

scala> val xs = List(1, 2, 3, 3)
xs: List[Int] = List(1, 2, 3, 3)

scala> xs.toSet map(_*2)
<console>:9: error: missing parameter type for expanded function ((x$1) => x$1.$times(2))
       xs.toSet map(_*2)

Однако, если назначено xs.toSet, оно компилируется.

scala> xs.toSet
res42: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> res42 map (_*2)
res43: scala.collection.immutable.Set[Int] = Set(2, 4, 6)

Кроме того, происходит другой путь, преобразование в Set из List и отображение наList соответствует.

scala> Set(5, 6, 7)
res44: scala.collection.immutable.Set[Int] = Set(5, 6, 7)

scala> res44.toList map(_*2)
res45: List[Int] = List(10, 12, 14)

Ответы [ 3 ]

21 голосов
/ 05 апреля 2011

В: Почему toSet не делает то, что я хочу?

A: Это было бы слишком просто.

В: Но почему это не компилируется? List(1).toSet.map(x => ...)

A: Компилятор Scala не может сделать вывод, что x является Int.

В: Что, это глупо?

A: Ну, List[A].toSet не возвращает immutable.Set[A]. Возвращает immutable.Set[B] для неизвестного B >: A.

В: Откуда мне было это знать?

A: Из Скаладока.

В: Но почему toSet определяется таким образом?

A: Возможно, вы предполагаете, что immutable.Set является ковариантным, но это не так. Это инвариант. И тип возврата toSet находится в ковариантной позиции, поэтому нельзя допускать, чтобы тип возврата был инвариантным.

В: Что вы подразумеваете под "ковариантной позицией"?

A: Позвольте мне Wikipedia, что для вас: http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science). См. Также главу 19 «Одерский, Веннерс и Ложка».

В: Теперь я понимаю. Но почему неизменяемый. Установите инвариант?

A: Позвольте мне переполнить стек, что для вас: Почему неизменяемый набор Скалы не ковариантен в своем типе?

Q: Я сдаюсь. Как мне исправить мой оригинальный код?

A: Это работает: List(1).toSet[Int].map(x => ...). Так же это: List(1).toSet.map((x: Int) => ...)

(с извинениями перед Фридманом и Феллайзеном. Спасибо Паульпу и Иджуме за помощь)

РЕДАКТИРОВАТЬ: Есть ценная дополнительная информация в Ответ Адриана и в обсуждении в комментариях и там и здесь.

15 голосов
/ 05 апреля 2011

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

def toSet[B >: A] => scala.collection.immutable.Set[B]

и компилятор должен будет вывести типы в двух местах вашего вызова. Альтернативой аннотированию параметра в вашей функции может быть вызов toSet с явным аргументом типа:

xs.toSet[Int] map (_*2)

ОБНОВЛЕНИЕ :

Что касается вашего вопроса, почему компилятор может вывести его за два шага, давайте просто посмотрим, что происходит, когда вы печатаете строки одну за другой:

scala> val xs = List(1,2,3)
xs: List[Int] = List(1, 2, 3)

scala> val ys = xs.toSet   
ys: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

Здесь компилятор выведет наиболее конкретный тип для ys, который в данном случае равен Set[Int]. Этот тип теперь известен, поэтому можно определить тип функции, переданной в map.

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

xs.toSet[Int].map[Int,Set[Int]](_*2)

где параметр второго типа используется для указания типа возвращаемой коллекции (подробнее смотрите, как реализованы коллекции Scala). Это означает, что я даже недооценил количество типов, которые должен выводить компилятор.

В этом случае может показаться, что легко сделать вывод Int, но в некоторых случаях это не так (учитывая другие особенности Scala, такие как неявные преобразования, синглтон-типы, черты в виде миксинов и т. Д.). Я не говорю, что это невозможно, просто компилятор Scala этого не делает.

13 голосов
/ 25 мая 2011

Я согласен, что было бы неплохо вывести «единственно возможный» тип, даже когда вызовы связаны, но есть технические ограничения.

Вы можете думать о логическом выводе, как о развертке в ширину выражения, собирающей ограничения (которые возникают из границ подтипа и требуемых неявных аргументов) для переменных типа, с последующим решением этих ограничений. Этот подход позволяет, например, влиять на вывод типа. В вашем примере, даже если есть единственное решение, если вы посмотрите только на подвыражение xs.toSet, последующие вызовы в цепочке могут ввести ограничения, которые делают систему неудовлетворительной. Недостатком оставления переменных типа нерешенными является то, что вывод типа для замыканий требует, чтобы целевой тип был известен, и, следовательно, потерпит неудачу (для этого требуется нечто конкретное - требуемый тип замыкания и тип его аргументов должны не оба будут неизвестны).

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

...