Подтвердить что ты не робот

Работа с коллекциями scala - CanBuildFrom проблемы

Я пытаюсь написать метод, который принимает любой тип коллекции CC[_] и сопоставляет его с новой коллекцией (тот же тип коллекции, но с другим типом элемента), и я борюсь за права. В основном я пытаюсь реализовать map, но не в самой коллекции.

Вопрос

Я пытаюсь реализовать метод с подписями, который выглядит примерно так:

def map[CC[_], T, U](cct: CC[T], f: T => U): CC[U]

Это будет:

map(List(1, 2, 3, 4), (_ : Int).toString) //would return List[String]

Мне интересен ответ, который также будет работать там, где CC есть Array, и меня интересует причина, по которой мои попытки (ниже) в конечном счете не сработали.


Мои попытки

(Для нетерпеливых, в дальнейшем, я полностью не могу заставить это работать. Повторить, вопрос: "Как я могу написать такой метод?" )

Я начинаю вот так:

scala> def map[T, U, CC[_]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct map f
                                                             ^
 <console>:9: error: value map is not a member of type parameter CC[T]
       cct map f
           ^

ОК, это имеет смысл - мне нужно сказать, что CC можно пропустить!

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct map f
<console>:10: error: type mismatch;
 found   : Traversable[U]
 required: CC[U]
       cct map f
           ^

Err, ОК! Возможно, если я действительно укажу этот экземпляр cbf. В конце концов, он указывает тип возврата (To) как CC[U]:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct.map(t => f(t))(cbf)
<console>:10: error: type mismatch;
 found   : scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]]
 required: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]]
       cct.map(t => f(t))(cbf)
                          ^

Err, ОК! Это более конкретная ошибка. Похоже, я могу это использовать!

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[Traversable[T], U, CC[U]]): CC[U] = 
     | cct.map(t => f(t))(cbf)
map: [T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]])CC[U]

Brilliant. У меня есть map! Позвольте использовать эту вещь!

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
<console>:11: error: Cannot construct a collection of type List[java.lang.String] with elements of type java.lang.String based on a collection of type Traversable[Int].
              map(List(1, 2, 3, 4), (_ : Int).toString)
                 ^

Скажите, что?


Наблюдения

Я действительно не могу не думать, что наблюдения Тони Морриса об этом в то время были абсолютно точными. Что он сказал? Он сказал: " Что бы это ни было, это не карта". Посмотрите, насколько это просто в стиле скаляз:

scala> trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] }
defined trait Functor

scala> def map[F[_]: Functor, A, B](fa: F[A], f: A => B): F[B] = implicitly[Functor[F]].fmap(fa)(f)
map: [F[_], A, B](fa: F[A], f: A => B)(implicit evidence$1: Functor[F])F[B]

Тогда

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
<console>:12: error: could not find implicit value for evidence parameter of type Functor[List]
              map(List(1, 2, 3, 4), (_ : Int).toString)
                 ^

Итак,

scala> implicit val ListFunctor = new Functor[List] { def fmap[A, B](fa: List[A])(f: A => B) = fa map f }
ListFunctor: java.lang.Object with Functor[List] = [email protected]

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
res5: List[java.lang.String] = List(1, 2, 3, 4)

Воспоминание о себе: слушать Тони!

4b9b3361

Ответ 1

То, в чем вы работаете, не обязательно CanBuildFrom или проблема Array vs. Seq. Вы работаете в String, который не является более высоким, но поддерживает map по сравнению с Char s.

SO: сначала отрисовка в дизайн коллекции Scala.

Что вам нужно, так это вывести как тип коллекции (например, String, Array[Int], List[Foo]), так и тип элемента (например, Char, Int, Foo, соответствующий приведенному выше).

Scala 2.10.x добавил несколько "классов классов", которые помогут вам. Например, вы можете сделать следующее:

class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) {
  final def filterMap[B, That](f: A => Option[B])(implicit cbf: CanBuildFrom[Repr, B, That]): That =
    r.flatMap(f(_).toSeq)
 }
 implicit def filterMap[Repr, A](r: Repr)(implicit fr: IsTraversableOnce[Repr]): FilterMapImpl[fr.A,Repr] =
   new FilterMapImpl(fr.conversion(r))

Здесь две части. FIRST, ваш класс, который использует коллекции, нуждается в двух параметрах типа: конкретном типе коллекции Repr и типе элементов A.

Далее вы определяете неявный метод, который принимает только тип коллекции Repr. Вы используете IsTraversableOnce (обратите внимание: есть также IsTraversableLike), чтобы захватить тип элемента этой коллекции. Вы видите, что это используется в сигнатуре типа FilterMapImpl[Repr, fr.A].

Теперь часть этого объясняется тем, что Scala не использует одну и ту же категорию для всех своих "функторных" операций. В частности, map - полезный метод для String. Я могу настроить все символы. Однако String может быть только Seq[Char]. Если я хочу определить Functor, то моя категория может содержать только тип Char и стрелки Char => Char. Эта логика фиксируется в CanBuildFrom. Однако, поскольку a String является Seq[Char], если вы пытаетесь использовать map в категории, поддерживаемой методом Seq map, тогда CanBuildFrom изменит ваш вызов на map.

Мы по существу определяем отношения "наследования" для наших категорий. Если вы попытаетесь использовать шаблон Functor, мы поместим подпись типа в наиболее определенную категорию, которую мы можем сохранить. Назовите это, что хотите; что является большим мотивирующим фактором для текущего дизайна коллекции.

End Digression, ответьте на вопрос

Теперь, поскольку мы пытаемся вывести много типов одновременно, я думаю, что эта опция имеет наименьшее количество аннотаций типа:

import collection.generic._

def map[Repr](col: Repr)(implicit tr: IsTraversableLike[Repr]) = new {
  def apply[U, That](f: tr.A => U)(implicit cbf: CanBuildFrom[Repr, U, That]) = 
    tr.conversion(col) map f
}


scala> map("HI") apply (_ + 1 toChar )
warning: there were 2 feature warnings; re-run with -feature for details
res5: String = IJ

Важно отметить, что IsTraversableLike фиксирует преобразование из Repr в TraversableLike, что позволяет использовать метод map.

Вариант 2

Мы также разбили вызов метода немного, чтобы Scala мог вывести типы Repr и U, прежде чем мы определим нашу анонимную функцию. Чтобы избежать аннотаций типа анонимных функций, мы должны знать все типы до того, как они появятся. Теперь мы все еще можем иметь Scala вывести некоторые типы, но терять вещи, которые неявно Traversable, если мы это сделаем:

import collection.generic._
import collection._
def map[Repr <: TraversableLike[A, Repr], A, U, That](col: Repr with TraversableLike[A,Repr])(f: A => U)(implicit cbf: CanBuildFrom[Repr, U, That]) = 
    col map f

Обратите внимание, что мы должны использовать Repr with TraversableLike[A,Repr]. Кажется, что большинство F-ограниченных типов требуют этого жонглирования.

В любом случае теперь посмотрим, что происходит на чем-то, что расширяет Traversable:

scala> map(List(40,41))(_ + 1 toChar )
warning: there were 1 feature warnings; re-run with -feature for details
res8: List[Char] = List(), *)

Это здорово. Однако, если мы хотим использовать одно и то же использование для Array и String, нам нужно немного поработать:

scala> map(Array('H', 'I'): IndexedSeq[Char])(_ + 1 toChar)(breakOut): Array[Char]
warning: there were 1 feature warnings; re-run with -feature for details
res14: Array[Char] = Array(I, J)

scala> map("HI": Seq[Char])(_ + 1 toChar)(breakOut) : String
warning: there were 1 feature warnings; re-run with -feature for details
res11: String = IJ

Для этого используются две части:

  • Мы должны использовать аннотацию типа для неявного преобразования из String/ArraySeq/IndexedSeq.
  • Нам нужно использовать breakOut для нашего CanBuildFrom и ввести аннотацию ожидаемого возвращаемого значения.

Это происходит только потому, что тип Repr <: TraversableLike[A,Repr] не включает String или Array, поскольку они используют неявные преобразования.

Вариант 3

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

SO, в основном, если вы хотите включить String и Array[T] в качестве коллекций, вам нужно перепрыгнуть через некоторые обручи. Это ограничение категории для отображения применяется как к функторам String, так и к BitSet в Scala.

Надеюсь, это поможет. Пинг меня, если у вас есть еще вопросы.

Ответ 2

На самом деле есть несколько вопросов...

Начните с последней попытки:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)
 (implicit cbf: CanBuildFrom[Traversable[T], U, CC[U]]): CC[U] = 
  cct.map(t => f(t))(cbf)

Этот компилятор компилируется, но не работает, потому что, согласно вашей сигнатуре типа, он должен искать неявный CanBuildFrom[Traversable[Int], String, List[String]] в области видимости, и его просто нет. Если бы вы создали один за другим, это сработало бы.

Теперь предыдущая попытка:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)
 (implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
  cct.map(t => f(t))(cbf)
<console>:10: error: type mismatch;
 found   : scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]]
 required: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]]
       cct.map(t => f(t))(cbf)
                          ^

Этот компилятор не компилируется, потому что неявный CanBuildFrom in Traversable жестко запрограммирован, чтобы принимать только Traversable как From. Однако, как указано в другом ответе, TraversableLike знает о фактическом типе коллекции (это его второй параметр типа), поэтому он определяет map с правильным CanBuildFrom[CC[T], U, CC[U]], и все счастливы. На самом деле, TraversableLike наследует этот map метод от scala.collection.generic.FilterMonadic, поэтому это еще более общий:

scala> import scala.collection.generic._
import scala.collection.generic._

scala> def map[T, U, CC[T] <: FilterMonadic[T, CC[T]]](cct: CC[T], f: T => U)
 |  (implicit cbf:  CanBuildFrom[CC[T], U, CC[U]]): CC[U] = cct.map(f)
warning: there were 1 feature warnings; re-run with -feature for details
map: [T, U, CC[T] <: scala.collection.generic.FilterMonadic[T,CC[T]]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]])CC[U]

scala> map(List(1,2,3,4), (_:Int).toString + "k")
res0: List[String] = List(1k, 2k, 3k, 4k)

Наконец, вышесказанное не работает с массивами, потому что Array не является FilterMonadic. Но есть неявное преобразование от Array до ArrayOps, а последнее реализует FilterMonadic. Поэтому, если вы добавили в него видимость, вы получите то, что работает и для массивов:

scala> import scala.collection.generic._
import scala.collection.generic._

scala> def map[T, U, CC[T]](cct: CC[T], f: T => U)
 |  (implicit cbf:  CanBuildFrom[CC[T], U, CC[U]], 
 |   ev: CC[T] => FilterMonadic[T,CC[T]]): CC[U] = cct.map(f)
warning: there were 1 feature warnings; re-run with -feature for details
map: [T, U, CC[T]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]], implicit ev: CC[T] => scala.collection.generic.FilterMonadic[T,CC[T]])CC[U]

scala> map(List(1,2,3,4), (_:Int).toString + "k")
res0: List[String] = List(1k, 2k, 3k, 4k)

scala> map(Array(1,2,3,4), (_:Int).toString + "k")
res1: Array[String] = Array(1k, 2k, 3k, 4k)

EDIT: Существует также способ заставить его работать для String и co: просто удалить более высокие типы в коллекции ввода/вывода, используя третий в середине:

def map[T, U, From, To, Middle](cct: From, f: T => U)
 (implicit ev: From => FilterMonadic[T, Middle], 
  cbf: CanBuildFrom[Middle,U,To]): To = cct.map(f)

Это работает на String и даже на Map[A,B]:

scala> map(Array(42,1,2), (_:Int).toString)
res0: Array[java.lang.String] = Array(42, 1, 2)

scala> map(List(42,1,2), (_:Int).toString)
res1: List[java.lang.String] = List(42, 1, 2)

scala> map("abcdef", (x: Char) => (x + 1).toChar)
res2: String = bcdefg

scala> map(Map(1 -> "a", 2 -> "b", 42 -> "hi!"), (a:(Int, String)) => (a._2, a._1))
res5: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, hi! -> 42)

Протестировано с помощью 2.9.2. Но, как отметил Джесер, в 2.10 есть замечательный IsTraversableLike, который лучше подходит для этого.

Ответ 3

Это он?

def map[A,B,T[X] <: TraversableLike[X,T[X]]]
  (xs: T[A])(f: A => B)(implicit cbf: CanBuildFrom[T[A],B,T[B]]): T[B] = xs.map(f)

map(List(1,2,3))(_.toString)
// List[String] = List(1, 2, 3)

См. также этот вопрос.