TL; DR đi thẳng đến ví dụ cuối cùng
Tôi sẽ thử và tóm tắt lại.
Định nghĩa
Phần forhiểu là một phím tắt cú pháp để kết hợp flatMapvà maptheo cách dễ đọc và lý luận.
Hãy đơn giản hóa mọi thứ một chút và giả sử rằng mọi classthứ cung cấp cả hai phương thức nói trên đều có thể được gọi là a monadvà chúng ta sẽ sử dụng ký hiệu M[A]để có nghĩa là a monadvới một kiểu bên trong A.
Ví dụ
Một số monads thường thấy bao gồm:
List[String] Ở đâu
M[X] = List[X]
A = String
Option[Int] Ở đâu
Future[String => Boolean] Ở đâu
M[X] = Future[X]
A = (String => Boolean)
bản đồ và bản đồ phẳng
Được xác định trong một đơn nguyên chung M[A]
def map(f: A => B): M[B]
def flatMap(f: A => M[B]): M[B]
ví dụ
val list = List("neo", "smith", "trinity")
val f: String => List[Int] = s => s.map(_.toInt).toList
list map f
>> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121))
list flatMap f
>> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)
để diễn đạt
Mỗi dòng trong biểu thức sử dụng <-ký hiệu được dịch thành một flatMaplệnh gọi, ngoại trừ dòng cuối cùng được dịch thành maplệnh gọi kết thúc , trong đó "biểu tượng liên kết" ở phía bên trái được truyền làm tham số cho hàm đối số (cái gì trước đây chúng tôi đã gọi f: A => M[B]):
for {
bound <- list
out <- f(bound)
} yield out
list.flatMap { bound =>
f(bound).map { out =>
out
}
}
list.flatMap { bound =>
f(bound)
}
list flatMap f
Biểu thức for chỉ có một <-được chuyển đổi thành maplệnh gọi với biểu thức được truyền dưới dạng đối số:
for {
bound <- list
} yield f(bound)
list.map { bound =>
f(bound)
}
list map f
Bây giờ đến điểm
Như bạn có thể thấy, phép maptoán bảo toàn "hình dạng" của bản gốc monad, vì vậy điều tương tự cũng xảy ra đối với yieldbiểu thức: a Listvẫn là a Listvới nội dung được biến đổi bởi phép toán trong yield.
Mặt khác, mỗi đường ràng buộc trong forchỉ là một thành phần của liên tiếp monads, phải được "làm phẳng" để duy trì một "hình dạng bên ngoài" duy nhất.
Giả sử trong một khoảnh khắc rằng mỗi ràng buộc bên trong được dịch thành một maplệnh gọi, nhưng bên phải có cùng A => M[B]chức năng, bạn sẽ kết thúc bằng một M[M[B]]cho mỗi dòng trong phần hiểu.
Mục đích của toàn bộ forcú pháp là để dễ dàng "san bằng" việc nối các phép toán đơn nguyên liên tiếp (tức là các phép toán "nâng" một giá trị trong "hình dạng đơn nguyên" A => M[B]:), với việc bổ sung một mapphép toán cuối cùng có thể thực hiện một chuyển đổi kết thúc.
Tôi hy vọng điều này giải thích logic đằng sau sự lựa chọn dịch, được áp dụng một cách máy móc, đó là: n flatMapcác lệnh gọi lồng nhau được kết luận bởi một maplệnh gọi duy nhất .
Một ví dụ minh họa có sẵn
Có nghĩa là để chỉ ra tính biểu cảm của forcú pháp
case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])
def getCompanyValue(company: Company): Int = {
val valuesList = for {
branch <- company.branches
consultant <- branch.consultants
customer <- consultant.portfolio
} yield (customer.value)
valuesList reduce (_ + _)
}
Bạn có thể đoán loại valuesList?
Như đã nói, hình dạng của dấu monadđược duy trì thông qua việc hiểu, vì vậy chúng ta bắt đầu bằng chữ Listin company.branches, và phải kết thúc bằng chữ a List.
Thay vào đó, loại bên trong sẽ thay đổi và được xác định bởi yieldbiểu thức: làcustomer.value: Int
valueList nên là một List[Int]