Неявное преобразование общих и неуниверсальных подтипов в Scala - PullRequest
2 голосов
/ 03 марта 2011

Предположим, вы хотите добавить некоторые методы ко всем элементам Iterables. Это может выглядеть так:

import collection.generic.CanBuildFrom

class Foo[P, S[X] <: Iterable[X]](val s : S[P]) {
  def bar(j : P)(implicit bf : CanBuildFrom[S[P],P,S[P]]) : S[P] = {
    val builder = bf(s)
    builder ++= s
    builder += j
    builder.result
  }

  def oneBar(j : P)(implicit bf : CanBuildFrom[S[P],P,S[P]]) : P = bar(j).head
}

implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)

Теперь, код как

println(Seq(1,2,3,4) bar 5)

компилируется и выполняется гладко. Тем не менее,

println((1 to 4) bar 5)

1010 * приводит *

error: value bar is not a member of scala.collection.immutable.Range.Inclusive 
with scala.collection.immutable.Range.ByOne

Я полагал, что это может быть связано с тем, что неявное преобразование требует (?), Чтобы тип параметра имел параметр типа (а Range - нет). Но

implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)

ничего не меняет. Обратите внимание, что Range расширяет Iterable[Int].

Что я делаю не так? Как я могу написать одно неявное преобразование, которое применяется ко всем подтипам Iterable, являются ли они общими или нет?

Редактировать: Я просто замечаю, что гораздо проще

implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)

работает как задумано (на REPL). Или это? Есть ли недостатки этого решения?

Редактировать 2: Недостатком является то, что статический тип результата bar будет только Iterable[P], а не более конкретным типом. Однако созданная коллекция имеет правильный (фактический) тип.

1 Ответ

2 голосов
/ 03 марта 2011

К сожалению, не имеет значения, что Range расширяет Iterable[Int], это действительно проблема с арностью типов params.Он тоже глубокий, даже основная библиотека страдает от этого местами (просто посмотрите на комментарии в Manifest)

Вы также столкнетесь с этим, если захотите использовать Карты, Строки и т. Д., Как будто онибыли Iterable.

Единственное решение, которое я нашел, - это определить несколько неявных преобразований в тип сутенера.

ОБНОВЛЕНИЕ

Проблемаздесь подразумевается вывод параметра типа P из предоставленного аргумента, который, по-видимому, не имеет параметра типа.По сути, вы пытаетесь сделать для конструктора типов то, что извлекатели будут делать для обычного конструктора, и полиморфизм мешает.

Ваш отредактированный пример работает, потому что этот конкретный вывод не нужен, подвохзаключается в том, что теперь вы можете вернуть только Iterable и, таким образом, потерять большую часть выгоды CanBuildFrom

Если это не проблема, тогда это более простое решение, так что катайтесь с ним.

В противном случае вам потребуются разные значения для каждого возможного числа типов, которые вы хотите отобразить.

ОБНОВЛЕНИЕ 2

Рассмотрите, как компилятор может обрабатывать ваши различныевыражения при попытке определить, является ли Range допустимым аргументом:

Take 1:

implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
  • S - тип с более высоким родом, типа * => *
  • Range - простой тип, типа *
  • Виды не совпадают, поэтому они недействительны

Take 2:

implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)
  • Та же проблема, S все еще имеет вид * => * аргумент делаетне соответствует

Взять 3:

implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)
  • Имея предоставленный параметр, Iterable[P] - простой тип *
  • Range проходит это первое препятствие
  • Вторая проверка состоит в том, что Range является подклассом Iterable[P] для некоторых P
  • Это с P, выведенным какInt
  • Компилятор рад, что все выводы, проверки границ и т. Д. Успешно завершены
...