2011-01-18 28 views
64

Tại sao việc xây dựng này gây ra lỗi Loại không phù hợp trong Scala?Loại không khớp trên Scala Để hiểu rõ

for (first <- Some(1); second <- List(1,2,3)) yield (first,second) 

<console>:6: error: type mismatch; 
found : List[(Int, Int)] 
required: Option[?] 
     for (first <- Some(1); second <- List(1,2,3)) yield (first,second) 

Nếu tôi chuyển Một số với danh sách nó biên dịch tốt:

for (first <- List(1,2,3); second <- Some(1)) yield (first,second) 
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1)) 

này cũng hoạt động tốt:

for (first <- Some(1); second <- Some(2)) yield (first,second) 
+2

Bạn mong đợi kết quả nào của Scala trong ví dụ không thành công? –

+0

Khi tôi viết nó, tôi nghĩ tôi sẽ nhận được một Option [List [(Int, Int)]]. –

Trả lời

99

Đối comprehensions được chuyển đổi thành các cuộc gọi đến map hoặc flatMap phương pháp . Ví dụ thế này:

for(x <- List(1) ; y <- List(1,2,3)) yield (x,y) 

trở thành rằng:

List(1).flatMap(x => List(1,2,3).map(y => (x,y))) 

Do đó, giá trị vòng đầu tiên (trong trường hợp này, List(1)) sẽ nhận được cuộc gọi flatMap phương pháp. Kể từ flatMap trên số List trả về một số List khác, kết quả của việc hiểu sẽ dĩ nhiên là List. (Đây là mới với tôi: Đối với comprehensions không phải lúc nào dẫn đến suối, thậm chí không nhất thiết trong Seq s.)

Bây giờ, hãy nhìn vào cách flatMap được khai báo trong Option:

def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B] 

Giữ điều này trong tâm trí. Hãy xem cách các sai lầm cho sự hiểu biết (một với Some(1)) được chuyển đổi sang một chuỗi các bản đồ gọi:

Some(1).flatMap(x => List(1,2,3).map(y => (x, y))) 

Bây giờ, thật dễ dàng để thấy rằng các tham số của flatMap gọi là cái gì đó trả về một List, nhưng không phải là Option, theo yêu cầu.

Để khắc phục điều này, bạn có thể làm như sau:

for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y) 

Đó biên dịch tốt. Cần lưu ý rằng Option không phải là loại phụ của Seq, như thường được giả định.

4

Có thể có liên quan đến Tùy chọn không phải là Iterable. Hàm ẩn Option.option2Iterable sẽ xử lý trường hợp trình biên dịch mong đợi thứ hai là một Iterable. Tôi hy vọng rằng ma thuật trình biên dịch khác nhau tùy thuộc vào loại biến vòng lặp.

24

Một mẹo dễ nhớ, để hiểu được sẽ cố gắng trả lại loại bộ sưu tập của trình tạo đầu tiên, Tùy chọn [Int] trong trường hợp này. Vì vậy, nếu bạn bắt đầu với Một số (1) bạn nên mong đợi kết quả của Tùy chọn [T].

Nếu bạn muốn có kết quả là Danh sách, bạn nên bắt đầu với trình tạo Danh sách.

Tại sao có hạn chế này và không cho rằng bạn luôn muốn một số loại chuỗi? Bạn có thể có một tình huống mà nó có ý nghĩa để trả lại Option.Có thể bạn có một số Option[Int] mà bạn muốn kết hợp với thứ gì đó để có được một số Option[List[Int]], với chức năng sau: (i:Int) => if (i > 0) List.range(0, i) else None; sau đó bạn có thể viết này và nhận None khi mọi thứ không "có ý nghĩa":

val f = (i:Int) => if (i > 0) Some(List.range(0, i)) else None 
for (i <- Some(5); j <- f(i)) yield j 
// returns: Option[List[Int]] = Some(List(0, 1, 2, 3, 4)) 
for (i <- None; j <- f(i)) yield j 
// returns: Option[List[Int]] = None 
for (i <- Some(-3); j <- f(i)) yield j 
// returns: Option[List[Int]] = None 

Làm thế nào cho comprehensions được mở rộng trong trường hợp nói chung là trong thực tế, một cơ chế khá chung để kết hợp một đối tượng kiểu M[T] với hàm (T) => M[U] để lấy đối tượng thuộc loại M[U]. Trong ví dụ của bạn, M có thể là Tùy chọn hoặc Danh sách. Nói chung nó phải cùng loại M. Vì vậy, bạn không thể kết hợp Tùy chọn với Danh sách. Ví dụ về những thứ khác có thể là M, hãy xem subclasses of this trait.

Tại sao kết hợp List[T] với (T) => Option[T] hoạt động mặc dù khi bạn bắt đầu với Danh sách? Trong trường hợp này thư viện sử dụng một loại tổng quát hơn, nơi nó có ý nghĩa. Vì vậy, bạn có thể kết hợp Danh sách với Traversable và có một chuyển đổi tiềm ẩn từ Option thành Traversable.

Điểm mấu chốt là: suy nghĩ về loại bạn muốn biểu thức trả về và bắt đầu với loại đó làm trình tạo đầu tiên. Bọc nó trong loại đó nếu cần thiết.