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 for
hiểu là một phím tắt cú pháp để kết hợp flatMap
và map
theo 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 class
thứ cung cấp cả hai phương thức nói trên đều có thể được gọi là a monad
và chúng ta sẽ sử dụng ký hiệu M[A]
để có nghĩa là a monad
vớ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 flatMap
lệnh gọi, ngoại trừ dòng cuối cùng được dịch thành map
lệ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 map
lệ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 map
toá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 yield
biểu thức: a List
vẫn là a List
vớ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 for
chỉ 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 map
lệ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ộ for
cú 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 map
phé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
flatMap
các lệnh gọi lồng nhau được kết luận bởi một map
lệ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 for
cú 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ữ List
in 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 yield
biểu thức: làcustomer.value: Int
valueList
nên là một List[Int]