5

Ich versuche, mehrere Kreuzprodukte von Traversables verschiedener (aber jeder homogene) Typen zu konstruieren. Der gewünschte Rückgabetyp ist ein Traversable eines Tupels mit dem Typ, der mit den Typen in den Eingabequerverweisen übereinstimmt. Zum Beispiel:Scala: Kreuz (kartesischen) Produkt mit mehreren Quellen und heterogenen Typen

List(1, 2, 3) cross Seq("a", "b") cross Set(0.5, 7.3) 

Dies sollte ein Traversable[(Int, String, Double)] mit allen möglichen Kombinationen aus den drei Quellen geben. Der Fall der Kombination von nur zwei Quellen war schön answered here. Die gegebene Idee ist:

implicit class Crossable[X](xs: Traversable[X]) { 
    def cross[A](ys: Traversable[A]) = for { x <- xs; y <- ys } yield (x, y) 
} 

Die Kommentare dort kurz das Problem der mehr Quellen erwähnen, aber ich suche eine Lösung zu finden, die nicht davon abhängen, entweder formlos oder scalaz (auf der anderen Seite, I don‘ Denken Sie daran, einige Boilerplate auf Tuple22 zu skalieren. Was würde Ich mag es, etwas zu tun, wie folgt aus:

implicit class Crossable[X](xs: Traversable[X]) { 
    def cross[A](ys: Traversable[A]) = for { x <- xs; y <- ys } yield (x, y) 
    def cross[A,B](ys: Traversable[(A,B)]) = // ... extend all Tuple2's in ys with x in xs to Tuple3's 
    def cross[A,B,C](ys: Traversable[(A,B,C)]) = // ... 
    // ... 
} 

Das funktioniert natürlich nicht aufgrund Typs Löschung (und leider erfordern würde wahrscheinlich Klammer in dem obigen Beispiel zu verwenden, da cross Recht assoziativ sein würde).

Meine Frage ist: Ist es irgendwie möglich, die Reflexionseigenschaften von Scala 2.10 auszunutzen, um das Problem zu lösen? Im Allgemeinen sollten sowohl A als auch X mit den verschiedenen Tupeltypen (und deren Typparametern, die herausfordernd sind) verglichen und mit größeren Tupeln zusammengeführt werden, um eine Lösung zu liefern, die das assoziative Gesetz erfüllt, richtig?

Antwort

5

hatte ich einen gehen sie an und kam mit dieser:

trait Crosser[A,B,C] { 
    def cross(as: Traversable[A], bs: Traversable[B]): Traversable[C] 
} 

trait LowPriorityCrosserImplicits { 
    private type T[X] = Traversable[X] 

    implicit def crosser2[A,B] = new Crosser[A,B,(A,B)] { 
    def cross(as: T[A], bs: T[B]): T[(A,B)] = for { a <- as; b <- bs } yield (a, b) 
    } 
} 

object Crosser extends LowPriorityCrosserImplicits { 
    private type T[X] = Traversable[X] 

    implicit def crosser3[A,B,C] = new Crosser[(A,B),C,(A,B,C)] { 
    def cross(abs: T[(A,B)], cs: T[C]): T[(A,B,C)] = for { (a,b) <- abs; c <- cs } yield (a, b, c) 
    } 

    implicit def crosser4[A,B,C,D] = new Crosser[(A,B,C),D,(A,B,C,D)] { 
    def cross(abcs: T[(A,B,C)], ds: T[D]): T[(A,B,C,D)] = for { (a,b,c) <- abcs; d <- ds } yield (a, b, c, d) 
    } 

    // and so on ... 
} 

implicit class Crossable[A](xs: Traversable[A]) { 
    def cross[B,C](ys: Traversable[B])(implicit crosser: Crosser[A,B,C]): Traversable[C] = crosser.cross(xs, ys) 
} 

Die Hauptidee ist es, die Arbeit zu einer Typklasse (Crosser) und implementieren die verschiedenen arities einfach durch die Spezialisierung für Traversable s aufzuschieben Tupel mit der entsprechenden Arity minus Eins. Einige Test in der REPL:

scala> List(1, 2, 3) cross Seq("a", "b") cross Set(0.5, 7.3) 
res10: Traversable[(Int, String, Double)] = List((1,a,0.5), (1,a,7.3), (1,b,0.5), (1,b,7.3), (2,a,0.5), (2,a,7.3), (2,b,0.5), (2,b,7.3), (3,a,0.5), (3,a,7.3), (3,b,0.5), (3,b,7.3)) 
+0

Wow, das ist ziemlich cool! Vielen Dank! Das Hinzufügen von rechts assoziativen Versionen scheint bei diesem Ansatz ebenfalls einfach zu sein. Ich bemerkte, dass Sie "mehrdeutige implizite" Compiler-Fehler durch die Einführung von "crosser2" in einer Eigenschaft (die sonst immer übereinstimmen würde) loswerden. Ich nehme an, dass es eine Art klassenhierarchieabhängige Prioritätsregel für implicits geben muss? Was mich noch rätselt: Warum sind 'crosser2',' crosser3', ... eigentlich im Umfang? Ich habe erwartet, dass ich Crosser importieren muss, um sie in Reichweite zu bringen, aber das scheint nicht der Fall zu sein. – bluenote10

+0

Falls jemand anderes das verwenden möchte: Ich habe gerade einen kleinen Code-Generator geschrieben (muss mal Makros lernen) und habe einen [Gist] hochgeladen (https://gist.github.com/bluenote10/5465957#file-crossproduct-scala) das alles bis zu einem vernünftig hohen Level enthält (ab dem 19. Typparameter habe ich komische Compilerfehler bekommen, aber 18 sollte mehr als genug für mich sein). – bluenote10

+1

Der Grund, warum Sie 'import Crosser._' nicht benötigen, ist, weil' Crosser' implizit in 'Crossable.cross' übergeben wird und implizite Auflösungsregeln sagen, dass wenn nach einem impliziten Wert vom Typ' T gesucht wird ', der Compiler wird automatisch in die Mitglieder des Companion-Objekts von' T' (falls vorhanden) schauen. Siehe SLS 7.2 –

Verwandte Themen