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)
карта и 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-выражение только с одним <-
преобразуется в map
вызов с выражением, переданным в качестве аргумента:
for {
bound <- list
} yield f(bound)
list.map { bound =>
f(bound)
}
list map f
Теперь к делу
Как видите, map
операция сохраняет «форму» оригинала monad
, то же самое происходит и с yield
выражением: a List
остается List
с содержимым, преобразованным операцией в yield
.
С другой стороны, каждая линия привязки в шаблоне for
- это просто последовательность monads
, которую необходимо «сплющить», чтобы сохранить единую «внешнюю форму».
Предположим на мгновение, что каждая внутренняя привязка была преобразована в map
вызов, но правая часть была той же самой A => M[B]
функцией, в результате вы получите a 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
in company.branches
и должны заканчиваться на List
.
Вместо этого внутренний тип изменяется и определяется yield
выражением:customer.value: Int
valueList
должен быть List[Int]