5

Tôi đang cố gắng để xây dựng nhiều sản phẩm chéo của traversables khác nhau (nhưng mỗi đồng nhất) các loại. Kiểu trả về mong muốn là một kiểu đi ngang của một bộ tuple với kiểu phù hợp với các kiểu trong các traversables đầu vào. Ví dụ:Scala: cross (Descartes) sản phẩm với nhiều nguồn và các loại không đồng nhất

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

Điều này phải cung cấp Traversable[(Int, String, Double)] với tất cả các kết hợp có thể có từ ba nguồn. Trường hợp kết hợp chỉ có hai nguồn là độc đáo answered here. Ý tưởng đưa ra là:

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

Các bình luận có đề cập ngắn gọn sự cố nguồn hơn, nhưng tôi đang tìm để tìm một giải pháp mà không phụ thuộc vào một trong hai hình thù hoặc scalaz (mặt khác, tôi don' t tâm có một số boilerplate để quy mô lên đến 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)]) = // ... 
    // ... 
} 

Điều này rõ ràng không hoạt động do chô bôi loại (và, thật không may, có lẽ sẽ đòi hỏi phải sử dụng dấu ngoặc đơn trong ví dụ trên, bởi vì cross sẽ là kết hợp đúng: một cái gì đó như sau những gì tôi muốn làm là).

Câu hỏi của tôi là: Có cách nào đó có thể khai thác các tính năng phản chiếu của Scala 2.10 để giải quyết vấn đề không? Nói chung, kết hợp cả hai loại AX với các loại bộ khác nhau (và các tham số kiểu của chúng, có vẻ khó khăn) và hợp nhất chúng thành các bộ dữ liệu lớn hơn sẽ cung cấp giải pháp thỏa mãn luật liên kết, đúng không?

Trả lời

5

Tôi đã có một lúc nó đi và đến với điều này:

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

Ý tưởng chính là để trì hoãn công việc để một lớp kiểu (Crosser) và thực hiện tất cả các arities khác nhau chỉ đơn giản bằng chuyên cho Traversable s của tuples với các arity tương ứng trừ một. Một số thử nghiệm trong 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

Thật tuyệt vời! Cảm ơn nhiều! Thêm các phiên bản kết hợp phù hợp cũng có vẻ đơn giản với cách tiếp cận này. Tôi nhận thấy bạn đã loại bỏ các lỗi trình biên dịch "không rõ ràng" bằng cách giới thiệu 'crosser2' trong một đặc điểm (nếu không sẽ luôn khớp). Tôi cho rằng phải có một số loại quy tắc ưu tiên phân cấp phụ thuộc vào hệ thống cấp bậc cho implicits? Điều gì vẫn còn khó hiểu với tôi: Tại sao «crosser2',' crosser3', ... thực sự nằm trong phạm vi? Tôi đã hy vọng rằng tôi phải "nhập khẩu Crosser._' để mang lại cho họ trong phạm vi, nhưng điều này dường như không phải là trường hợp. – bluenote10

+0

Trong trường hợp bất kỳ ai khác muốn sử dụng điều này: Tôi vừa viết một trình tạo mã nhỏ (gotta tìm hiểu macro một ngày) và tải lên [Gist] (https://gist.github.com/bluenote10/5465957#file-crossproduct-scala) có chứa tất cả các boilerplate lên đến một mức độ hợp lý cao (bắt đầu với tham số loại 19 tôi đã nhận lỗi trình biên dịch lạ, nhưng 18 nên được quá đủ cho tôi). – bluenote10

+1

Lý do tại sao bạn không cần làm 'import Crosser._', là vì' Crosser' được truyền hoàn toàn trong 'Crossable.cross', và các quy tắc phân giải ngầm nói rằng khi tìm kiếm một giá trị ngầm định của kiểu' T ', trình biên dịch sẽ tự động xem xét các thành viên của đối tượng đồng hành của' T' (nếu có). Xem SLS 7.2 –

Các vấn đề liên quan