TL; DR перейти безпосередньо до останнього прикладу
Я спробую підбити підсумок.
Визначення
for
Розуміння є синтаксис ярлика , щоб об'єднати flatMap
і map
таким чином , який легко читати і міркувати о.
Давайте трохи спростимо ситуацію і припустимо, що кожен, class
що забезпечує обидва вищезазначені методи, може бути названий a, monad
і ми будемо використовувати символ M[A]
для позначення a monad
з внутрішнім типом A
.
Приклади
Деякі часто зустрічаються монади включають:
List[String]
де
M[X] = List[X]
A = String
Option[Int]
де
Future[String => Boolean]
де
M[X] = Future[X]
A = (String => Boolean)
map and flatMap
Визначається у загальній монаді M[A]
def map(f: A => B): M[B]
def flatMap(f: A => M[B]): M[B]
напр
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)
для виразу
Кожен рядок у виразі, що використовує <-
символ, перекладається у flatMap
виклик, за винятком останнього рядка, який перекладається в завершальний map
виклик, де "зв'язаний символ" зліва передається як параметр функції аргументу (що ми раніше називали 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
Вираз for-only з одним <-
перетворюється у map
виклик із виразом, переданим як аргумент:
for {
bound <- list
} yield f(bound)
list.map { bound =>
f(bound)
}
list map f
Тепер до суті
Як бачите, map
операція зберігає "форму" оригіналу monad
, тому те саме відбувається з yield
виразом: a List
залишається a List
зі змістом, перетвореним операцією в yield
.
З іншого боку, кожна зв'язувальна лінія в for
є просто послідовною композицією monads
, яку потрібно "сплющити", щоб зберегти єдину "зовнішню форму".
Припустимо на мить, що кожна внутрішня прив'язка була переведена на map
виклик, але права рука була тією самою A => M[B]
функцією, ви отримаєте M[M[B]]
для кожного рядка в розумінні.
Метою цілого for
синтаксису є легке "згладжування" об'єднання послідовних монадичних операцій (тобто операцій, що "піднімають" значення у "монадичній формі":) A => M[B]
, з додаванням заключної map
операції, яка, можливо, виконує завершальну трансформацію.
Сподіваюся, це пояснює логіку вибору перекладу, який застосовується механічним способом, тобто: n
flatMap
вкладені виклики, укладені одним map
викликом.
Надуманий ілюстративний приклад,
призначений для демонстрації виразності for
синтаксису
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 (_ + _)
}
Чи можете ви вгадати тип valuesList
?
Як вже було сказано, форма а monad
підтримується через розуміння, тому ми починаємо з " List
в" company.branches
і повинні закінчуватися "а" List
.
Внутрішній тип замість цього змінюється і визначається yield
виразом: який єcustomer.value: Int
valueList
має бути a List[Int]