5

Próbuję skonstruować wiele produktów krzyżowych z materiałami traserskimi różnych (ale każdy homogeniczny) typów. Żądany typ zwrotu to ruchoma krotka z typem pasującym do typów w wejściowych przejściach. Na przykład:Scala: produkt krzyżowy (kartezjański) z wieloma źródłami i heterogenicznymi typami.

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

To powinno dać Traversable[(Int, String, Double)] ze wszystkimi możliwymi kombinacjami z trzech źródeł. Przypadek połączenia tylko dwóch źródeł był ładnie answered here. Dany pomysł jest:

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

Uwagi tam pokrótce problem większej liczby źródeł, ale szukam, aby znaleźć rozwiązanie, które nie zależą albo bezkształtne lub scalaz (z drugiej strony, I don” t mind o pewnej płycie do skali do Tuple22). Co chciałbym zrobić coś jak następuje:

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)]) = // ... 
    // ... 
} 

To oczywiście nie działa ze względu na typ skasowaniem (i, niestety, prawdopodobnie wymagać, aby użyć nawiasów w powyższym przykładzie, ponieważ cross byłoby prawo asocjacyjne).

Moje pytanie brzmi: czy w jakiś sposób można wykorzystać funkcje odbicia Scala 2.10, aby rozwiązać problem? Ogólnie rzecz biorąc, dopasowanie obu typów krotek (i ich parametrów typu, co wydaje się wyzwaniem) i połączenie ich z większymi krotkami powinno zapewnić rozwiązanie satysfakcjonujące asocjacyjne prawo, prawda?

Odpowiedz

5

miałem iść na niego i podszedł z tym:

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

Główną ideą jest, aby odroczyć pracę do klasy typu (Crosser) i wdrożyć wszystkie różne arities prostu przez specjalizującą się w Traversable s krotek z odpowiednią arytją minus jeden. Niektóre badania w 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, to jest całkiem fajne! Dziękuję Ci bardzo! Dodanie prawych wersji asocjacyjnych również wydaje się proste dzięki temu podejściu. Zauważyłem, że pozbyłeś się "niejednoznacznych" błędów kompilatora, wprowadzając 'crosser2' w cechę (która w przeciwnym razie zawsze by pasowała). Zakładam, że dla implicite musi istnieć jakaś reguła priorytetu hierarchii zależnej od klasy? Co wciąż mnie zastanawia: dlaczego 'crosser2',' crosser3', ... faktycznie w zakresie? Spodziewałem się, że będę musiał "zaimportować Crosser._", aby wprowadzić je w zakres, ale wydaje się, że tak nie jest. – bluenote10

+0

Na wypadek, gdyby ktoś chciał z tego skorzystać: właśnie napisałem mały generator kodu (muszę nauczyć się makr) i przesłałem [Gist] (https://gist.github.com/bluenote10/5465957#file-crossproduct-scala) zawierająca wszystkie dane z zestawu do racjonalnie wysokiego poziomu (począwszy od parametru 19 typu otrzymałem dziwne błędy kompilatora, ale 18 powinno być dla mnie więcej niż wystarczające). – bluenote10

+1

Powodem, dla którego nie musisz wykonywać "importu Crosser._", jest to, że 'Crosser' jest przekazywany niejawnie w' Crossable.cross', a domyślne reguły rozdzielczości mówią, że podczas wyszukiwania niejawnej wartości typu 'T ', kompilator automatycznie przejrzy elementy obiektu towarzyszącego' T' (jeśli istnieje). Zobacz SLS 7.2 –

Powiązane problemy