TL; DR vá diretamente para o exemplo final
Vou tentar recapitular.
Definições
A for
compreensão é um atalho de sintaxe para combinar flatMap
e map
de uma forma fácil de ler e raciocinar.
Vamos simplificar um pouco as coisas e assumir que todos os class
que fornecem os dois métodos mencionados acima podem ser chamados de a monad
e usaremos o símbolo M[A]
para significar a monad
com um tipo interno A
.
Exemplos
Algumas mônadas comumente vistas incluem:
List[String]
Onde
M[X] = List[X]
A = String
Option[Int]
Onde
Future[String => Boolean]
Onde
M[X] = Future[X]
A = (String => Boolean)
mapa e mapa plano
Definido em uma mônada genérica M[A]
/* applies a transformation of the monad "content" mantaining the
* monad "external shape"
* i.e. a List remains a List and an Option remains an Option
* but the inner type changes
*/
def map(f: A => B): M[B]
/* applies a transformation of the monad "content" by composing
* this monad with an operation resulting in another monad instance
* of the same type
*/
def flatMap(f: A => M[B]): M[B]
por exemplo
val list = List("neo", "smith", "trinity")
//converts each character of the string to its corresponding code
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)
para expressão
Cada linha na expressão que usa o <-
símbolo é traduzida para uma flatMap
chamada, exceto para a última linha que é traduzida para uma map
chamada final , onde o "símbolo vinculado" no lado esquerdo é passado como o parâmetro para a função do argumento (o que que chamamos anteriormente f: A => M[B]
):
// The following ...
for {
bound <- list
out <- f(bound)
} yield out
// ... is translated by the Scala compiler as ...
list.flatMap { bound =>
f(bound).map { out =>
out
}
}
// ... which can be simplified as ...
list.flatMap { bound =>
f(bound)
}
// ... which is just another way of writing:
list flatMap f
Uma for-expression com apenas um <-
é convertida em uma map
chamada com a expressão passada como argumento:
// The following ...
for {
bound <- list
} yield f(bound)
// ... is translated by the Scala compiler as ...
list.map { bound =>
f(bound)
}
// ... which is just another way of writing:
list map f
Agora ao ponto
Como você pode ver, a map
operação preserva a "forma" do original monad
, então o mesmo acontece para a yield
expressão: a List
permanece List
com o conteúdo transformado pela operação no yield
.
Por outro lado, cada linha de ligação no for
é apenas uma composição de sucessivas monads
, que devem ser "achatadas" para manter uma única "forma externa".
Suponha por um momento que cada ligação interna foi traduzida para uma map
chamada, mas a mão direita era a mesma A => M[B]
função, você terminaria com um M[M[B]]
para cada linha na compreensão.
A intenção de toda a for
sintaxe é facilmente "nivelar" a concatenação de operações monádicas sucessivas (ou seja, operações que "levantam" um valor em uma "forma monádica":) A => M[B]
, com a adição de uma map
operação final que possivelmente executa uma transformação conclusiva.
Espero que isso explique a lógica por trás da escolha da tradução, que é aplicada de forma mecânica, ou seja: n
flatMap
chamadas aninhadas concluídas por uma única map
chamada.
Um exemplo ilustrativo inventado
destinado a mostrar a expressividade da for
sintaxe
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 (_ + _)
}
Você consegue adivinhar o tipo de valuesList
?
Como já foi dito, a forma do monad
é mantida através da compreensão, então começamos com um List
dentro company.branches
e devemos terminar com um List
.
Em vez disso, o tipo interno muda e é determinado pela yield
expressão: que écustomer.value: Int
valueList
devia ser um List[Int]