2010-07-12 11 views
7

Angenommen, ich möchte hinzufügen, Funktionalität wie map zu einer Scala List, etwas in Richtung der list mapmap f, die die Funktion f auf jedes Element von list zweimal anwendet. (Ein ernsteres Beispiel könnte sein, eine parallele oder verteilte Karte Implementierung, aber ich will nicht in dieser Richtung von Details ablenken lassen.)Kann ich meine Bibliothek mit einem Analogon von TraversableLike.map "pimpen", das nette Variantentypen hat?

Mein erster Ansatz

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)) } } 
    } 
} 

dies jetzt wäre funktioniert super

scala> import MapMap._ 
import MapMap._ 

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

außer natürlich ist es nur für List s, und es gibt keinen Grund, warum wir sollten dies für etwas Traverseable mit einer map Funktion nicht arbeiten wollen, zB Set s oder Stream s. So ist der zweite Versuch, sieht aus wie

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)) } } 
    } 
} 

Aber jetzt, natürlich kann das Ergebnis nicht zu einem List[A] zugeordnet werden:

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] 

Gibt es einen Mittelweg? Kann ich eine implizite Konvertierung schreiben, die allen Unterklassen von Traversable eine Methode hinzufügt und Objekte mit diesem Typ erfolgreich zurückgibt?

(Ich denke, dies beinhaltet die gefürchtete CanBuildFrom Eigenschaft zu verstehen, und vielleicht sogar breakout!)

Antwort

10

Sie das nicht für alle Traversables tun können, da sie don Ich garantiere, dass die Map etwas Spezifischeres als Traversable zurückgibt. Siehe Update 2 unten.

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 Ich habe eine andere gepimpt Methode hinzugefügt, mapToString, zu zeigen, warum TraversableW zwei Typparameter akzeptiert, sondern als einen Parameter wie in Alexey Lösung. Der Parameter CC ist ein höherer Typ, er stellt den Containertyp der ursprünglichen Sammlung dar. Der zweite Parameter A repräsentiert den Elementtyp der ursprünglichen Sammlung. Die Methode mapToString kann somit den ursprünglichen Containertyp mit einem anderen Elementtyp zurückgeben: CC[String.

UPDATE 2 Dank @oxbow_lakes Kommentar habe ich diese überdacht. Es ist in der Tat möglich, direkt zu pimpen CC[X] <: Traversable[X], TraversableLike ist nicht unbedingt erforderlich. Kommentare inline:

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)) 
} 

Was ist der Unterschied?Wir mussten collection.breakOut verwenden, da wir den spezifischen Sammlungsuntertyp nicht von Traversable[A] wiederherstellen können.

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 
} 

Die Builderb wird mit der ursprünglichen Sammlung initialisiert, der der Mechanismus ist, den dynamischen Typ durch eine map zu bewahren. Allerdings desavouiert unsere CanBuildFrom alles Wissen der Von, durch die Art Argument Nothing. Alles, was Sie mit Nothing tun können, ist es zu ignorieren, was genau das ist, was breakOut tut:

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() 
    } 

Wir b.apply(from) nicht nennen kann, nicht mehr als Sie def foo(a: Nothing) = 0 nennen könnte.

+0

Funktioniert perfekt! –

+2

Ich nahm einen etwas anderen Ansatz in Scalaz, die ein bisschen stärker ist: http://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/CanBuildAnySelf.scala#L24 http: //github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Functor.scala#L28 – retronym

+0

Ich habe nicht die Rechte, es zu bearbeiten, retronym, aber vielleicht das formatierte des Codes Block sollte erweitert werden, um die Importe in Ihrem Beispiel aufzunehmen? Prost! – pr1001

5

Als allgemeine Regel gilt, wenn Sie Objekte mit der gleichen Art zurückkehren möchten, müssen Sie TraversableLike (IterableLike, SeqLike usw.) anstelle von Traversable. Hier ist die allgemeinste Version, die ich tun konnte, (die separate FancyTraversable Klasse ist es zu vermeiden, Strukturtypen Folgern und die Reflexion Hit):

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) 
+0

Muss ich etwas importieren? Ich bekomme einen "Fehler: nicht gefunden: Typ TraversableLike". (2.8.0RC7) –

+0

"import scala.collection._" und "import scala.collection.generic._" zumindest macht es kompilieren, aber jetzt "List (1,2,3) mapmap {_ + 1}" nur gibt mir "Fehler: Wert Mapmap ist kein Mitglied von List [Int]". –