Scala: перекрестное (декартово) произведение с несколькими источниками и разнородными типами
Я пытаюсь построить несколько перекрестных произведений проходимых различных (но каждый гомогенных) типов. Требуемый тип возвращаемого значения - это обход кортежа с типом, совпадающим с типами во входных обходных путях. Например:
List(1, 2, 3) cross Seq("a", "b") cross Set(0.5, 7.3)
Это должно дать Traversable[(Int, String, Double)]
со всеми возможными комбинациями из трех источников. На случай объединения только двух источников здесь был дан хороший ответ. Данная идея:
implicit class Crossable[X](xs: Traversable[X]) {
def cross[A](ys: Traversable[A]) = for { x <- xs; y <- ys } yield (x, y)
}
Комментарии там кратко упоминают проблему большего количества источников, но я ищу, чтобы найти решение, которое не зависит ни от бесформенного, ни от скаляша (с другой стороны, я не против иметь некоторый шаблон для масштабирования до Tuple22
). Я хотел бы сделать что-то вроде следующего:
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)]) = // ...
// ...
}
Это очевидно не работает из-за стирания типа (и, к сожалению, вероятно, потребуется использовать круглые скобки в приведенном выше примере, потому что cross
было бы правильно ассоциативно).
Мой вопрос: возможно ли как-то использовать возможности отражения Scala 2.10 для решения проблемы? В общем, совпадают оба A
а также X
для различных типов кортежей (и их типов, что кажется сложным), и объединение их в более крупные кортежи должно обеспечить решение, удовлетворяющее ассоциативному закону, верно?
1 ответ
Я попробовал это и придумал это:
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 )
}
Основная идея - отложить работу до класса типов (Crosser
) и реализовать все различные арты просто, специализируясь на Traversable
с кортежей с соответствующей арностью минус один. Некоторый тест в 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))