Могу ли я "улучшить свою библиотеку" с помощью аналога TraversableLike.map, который имеет красивые варианты типов? - PullRequest
7 голосов
/ 12 июля 2010

Предположим, я хочу добавить такую ​​функциональность, как map к Scala List, что-то вроде list mapmap f, которая применяет функцию f к каждому элементу list дважды. (Более серьезным примером может быть реализация параллельной или распределенной карты, но я не хочу отвлекаться на детали в этом направлении.)

Мой первый подход будет

object MapMap {
    implicit def createFancyList[A](list: List[A]) = new Object {
        def mapmap(f: A => A): List[A] = { list map { a: A => f(f(a)) } }
    }
}

теперь это прекрасно работает

scala> import MapMap._
import MapMap._

scala> List(1,2,3) mapmap { _ + 1 }
res1: List[Int] = List(3, 4, 5)

за исключением, конечно, только для List с, и нет никаких причин, по которым мы не должны хотеть, чтобы это работало для чего-либо Traverseable, с функцией map, например. Set с или Stream с. Итак, вторая попытка выглядит как

object MapMap2 {
    implicit def createFancyTraversable[A](t: Traversable[A]) = new Object {
        def mapmap(f: A => A): Traversable[A] = { t map { a: A => f(f(a)) } }
    }
}

Но теперь, конечно, результат не может быть присвоен List[A]:

scala> import MapMap2._
import MapMap2._

scala> val r: List[Int] = List(1,2,3) mapmap { _ + 1 }
<console>:9: error: type mismatch;
 found   : Traversable[Int]
 required: List[Int]

Есть ли какая-то золотая середина? Могу ли я написать неявное преобразование, которое добавляет метод ко всем подклассам Traversable и успешно возвращает объекты с этим типом?

(Полагаю, это подразумевает понимание ужасной черты CanBuildFrom, а может быть, даже breakout!)

Ответы [ 2 ]

11 голосов
/ 12 июля 2010

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

import collection.generic.CanBuildFrom
import collection.TraversableLike

class TraversableW[CC[X] <: TraversableLike[X, CC[X]], A](value: CC[A]) {
  def mapmap(f: A => A)(implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): CC[A] 
      = value.map(f andThen f)
  def mapToString(implicit cbf: CanBuildFrom[CC[A], String, CC[String]]): CC[String]
      = value.map(_.toString)
}

object TraversableW {
  implicit def TraversableWTo[CC[X] <: TraversableLike[X, CC[X]], A](t: CC[A]): TraversableW[CC, A] 
      = new TraversableW[CC, A](t)
}

locally {
  import TraversableW._

  List(1).mapmap(1+)
  List(1).mapToString
  // The static type of Seq is preserved, *and* the dynamic type of List is also
  // preserved.
  assert((List(1): Seq[Int]).mapmap(1+) == List(3))
}

UPDATE Я добавил еще один метод pimped, mapToString, чтобы продемонстрировать, почему TraversableW принимает два параметра типа, а не один параметр, как в решении Алексея. Параметр CC является типом с более высоким родом, он представляет тип контейнера исходной коллекции. Второй параметр A представляет тип элемента исходной коллекции. Таким образом, метод mapToString может возвращать исходный тип контейнера с другим типом элемента: CC[String.

ОБНОВЛЕНИЕ 2 Благодаря комментарию @oxbow_lakes я переосмыслил это. Это действительно возможно, чтобы непосредственно сутенер CC[X] <: Traversable[X], TraversableLike строго не нужно. Встроенные комментарии:

import collection.generic.CanBuildFrom
import collection.TraversableLike

class TraversableW[CC[X] <: Traversable[X], A](value: CC[A]) {
  /**
   * A CanBuildFromInstance based purely the target element type `Elem`
   * and the target container type `CC`. This can be converted to a
   * `CanBuildFrom[Source, Elem, CC[Elem]` for any type `Source` by
   * `collection.breakOut`.
   */
  type CanBuildTo[Elem, CC[X]] = CanBuildFrom[Nothing, Elem, CC[Elem]]

  /**
   * `value` is _only_ known to be a `Traversable[A]`. This in turn
   * turn extends `TraversableLike[A, Traversable[A]]`. The signature
   * of `TraversableLike#map` requires an implicit `CanBuildFrom[Traversable[A], B, That]`,
   * specifically in the call below `CanBuildFrom[Traversable[A], A CC[A]`.
   *
   * Essentially, the specific type of the source collection is not known in the signature
   * of `map`.
   *
   * This cannot be directly found instead we look up a `CanBuildTo[A, CC[A]]` and
   * convert it with `collection.breakOut`
   *
   * In the first example that referenced `TraversableLike[A, CC[A]]`, `map` required a
   * `CanBuildFrom[CC[A], A, CC[A]]` which could be found.
   */
  def mapmap(f: A => A)(implicit cbf: CanBuildTo[A, CC]): CC[A]
      = value.map[A, CC[A]](f andThen f)(collection.breakOut)
  def mapToString(implicit cbf: CanBuildTo[String, CC]): CC[String]
      = value.map[String, CC[String]](_.toString)(collection.breakOut)
}

object TraversableW {
  implicit def TraversableWTo[CC[X] <: Traversable[X], A](t: CC[A]): TraversableW[CC, A]
      = new TraversableW[CC, A](t)
}

locally {
  import TraversableW._

  assert((List(1)).mapmap(1+) == List(3))

  // The static type of `Seq` has been preserved, but the dynamic type of `List` was lost.
  // This is a penalty for using `collection.breakOut`. 
  assert((List(1): Seq[Int]).mapmap(1+) == Seq(3))   
}

Какая разница? Нам пришлось использовать collection.breakOut, потому что мы не можем восстановить определенный подтип коллекции из простого Traversable[A].

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
  val b = bf(repr)
  b.sizeHint(this) 
  for (x <- this) b += f(x)
  b.result
}

Builder b инициализируется с исходной коллекцией, которая является механизмом сохранения динамического типа через map. Однако наш CanBuildFrom дезавуировал все знания о From посредством аргумента типа Nothing. Все, что вы можете сделать с Nothing, это игнорировать его, что и делает breakOut:

def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
  new CanBuildFrom[From, T, To] {
    def apply(from: From) = b.apply();
    def apply() = b.apply()
  }

Мы не можем позвонить b.apply(from), не больше, чем вы могли бы позвонить def foo(a: Nothing) = 0.

5 голосов
/ 12 июля 2010

Как правило, когда вы хотите вернуть объекты одного типа, вам нужно TraversableLike (IterableLike, SeqLike и т. Д.) Вместо Traversable.Вот самая общая версия, которую я мог придумать (отдельный класс FancyTraversable существует для того, чтобы избежать вывода структурных типов и попадания отражения):

class FancyTraversable[A, S <: TraversableLike[A, S]](t: S) {
  def mapmap(f: A => A)(implicit bf: CanBuildFrom[S,A,S]): S = { t map { a: A => f(f(a)) } }
}

implicit def createFancyTraversable[A, S <: TraversableLike[A, S]](t: S): FancyTraversable[A, S] = new FancyTraversable(t)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...