Nhập Không phù hợp trên Scala để hiểu


81

Tại sao cấu trúc này gây ra lỗi Loại không khớp trong Scala?

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

Điều này cũng hoạt động tốt:

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

2
Bạn mong đợi Scala sẽ trả về kết quả gì trong ví dụ thất bại?
Daniel C. Sobral

1
Khi tôi viết nó, tôi nghĩ rằng tôi sẽ nhận được một Tùy chọn [Danh sách [(Int, Int)]].
Felipe Kamakura

Câu trả lời:


117

Đối với phần hiểu được chuyển đổi thành các cuộc gọi đến phương thức maphoặc flatMap. Ví dụ cái này:

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

trở thành:

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

Do đó, giá trị vòng lặp đầu tiên (trong trường hợp này, List(1)) sẽ nhận được flatMaplời gọi phương thức. Vì flatMaptrên một Listtrả về một khác List, kết quả của cho sự hiểu tất nhiên sẽ là a List. (Điều này mới đối với tôi: Vì sự hiểu biết không phải lúc nào cũng dẫn đến luồng, thậm chí không nhất thiết phải bằng Seqs.)

Bây giờ, hãy xem 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 làm thế nào để hiểu sai (cái có Some(1)) được chuyển đổi thành một chuỗi các lệnh gọi bản đồ:

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

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

Để khắc phục sự cố, bạn có thể làm như sau:

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

Điều đó biên dịch tốt. Cần lưu ý rằng đó Optionkhông phải là một kiểu con của Seq, như thường được giả định.


31

Một mẹo dễ nhớ, để hiểu rõ sẽ cố gắng trả về loại tập hợ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 Some (1), bạn nên mong đợi kết quả của Lựa chọn [T].

Nếu bạn muốn một kết quả của loại Danh sách , bạn nên bắt đầu với một trình tạo Danh sách.

Tại sao có hạn chế này và không cho rằng bạn sẽ luôn muốn một số loại trình tự? Bạn có thể có một tình huống hợp lý để quay trở lại Option. Có thể bạn có một Option[Int]mà bạn muốn kết hợp với một cái gì đó để có được một Option[List[Int]], nói với các chức năng sau: (i:Int) => if (i > 0) List.range(0, i) else None; sau đó bạn có thể viết điều này và nhận được Không 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 để hiểu được mở rộng trong trường hợp chung thực chất là một cơ chế khá chung để kết hợp một đối tượng kiểu M[T]với một hàm (T) => M[U]để có được một đối tượng kiểu 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 là phải cùng loại M. Vì vậy bạn không thể kết hợp Option với List. Để biết ví dụ về những thứ khác có thể có M, hãy xem các lớp con của đặc điểm này .

Tại sao lại kết hợp List[T]với (T) => Option[T]công việc 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 kiểu tổng quát hơn nếu nó có ý nghĩa. Vì vậy, bạn có thể kết hợp Danh sách với Có thể chuyển qua và có một sự chuyển đổi ngầm từ Tùy chọn sang Có thể chuyển qua.

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


Tôi cho rằng đó là một lựa chọn thiết kế tồi khi tạo forcú pháp thông thường thực hiện kiểu giải mã chức năng / đơn nguyên này. Tại sao không có các phương thức được đặt tên khác nhau để ánh xạ functor / monad, như fmap, v.v. và dự trữ forcú pháp để có một hành vi cực kỳ đơn giản phù hợp với mong đợi đến từ hầu như bất kỳ ngôn ngữ lập trình chính thống nào khác?
ely

Bạn có thể làm cho loại nội dung fmap / lift riêng biệt trở nên chung chung như bạn muốn mà không cần thực hiện một tuyên bố luồng điều khiển máy tính tuần tự chính thống trở nên rất đáng ngạc nhiên và có các biến chứng hiệu suất sắc thái, v.v. "Mọi thứ đều hoạt động với" không đáng như vậy.
ely

4

Nó có thể liên quan đến Option không phải là một Lặp lại. Hàm ngầm Option.option2Iterablesẽ xử lý trường hợp trình biên dịch đang mong đợi thứ hai là một Lặp lại. Tôi mong đợi rằng phép thuật của trình biên dịch là khác nhau tùy thuộc vào loại biến vòng lặp.


1

Tôi luôn thấy điều này hữu ích:

scala> val foo: Option[Seq[Int]] = Some(Seq(1, 2, 3, 4, 5))
foo: Option[Seq[Int]] = Some(List(1, 2, 3, 4, 5))

scala> foo.flatten
<console>:13: error: Cannot prove that Seq[Int] <:< Option[B].
   foo.flatten
       ^

scala> val bar: Seq[Seq[Int]] = Seq(Seq(1, 2, 3, 4, 5))
bar: Seq[Seq[Int]] = List(List(1, 2, 3, 4, 5))

scala> bar.flatten
res1: Seq[Int] = List(1, 2, 3, 4, 5)

scala> foo.toSeq.flatten
res2: Seq[Int] = List(1, 2, 3, 4, 5)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.