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]
Lists
. Se vocêmap
duas vezes uma funçãoA => List[B]
(que é uma das operações monádicas essenciais) sobre algum valor, você termina com uma Lista [Lista [B]] (estamos assumindo que os tipos correspondem). O loop interno de compreensão de for compõe essas funções com aflatMap
operação correspondente , "achatando" a forma de Lista [Lista [B]] em uma Lista simples [B] ... Espero que esteja claroyield
cláusula écustomer.value
, cujo tipo éInt
, portanto, o todofor comprehension
avalia como aList[Int]
.Não sou um scala mega mind, então sinta-se à vontade para me corrigir, mas é assim que eu explico a
flatMap/map/for-comprehension
saga para mim mesmo!Para entender
for comprehension
e sua traduçãoscala's map / flatMap
, devemos dar pequenos passos e entender as partes que o compõem -map
eflatMap
. Mas não éscala's flatMap
sómap
comflatten
você se perguntar! em caso afirmativo, por que tantos desenvolvedores acham tão difícil entendê-lo ou dominá-lofor-comprehension / flatMap / map
. Bem, se você olhar apenas para scalamap
eflatMap
assinatura, verá que eles retornam o mesmo tipo de retornoM[B]
e funcionam no mesmo argumento de entradaA
(pelo menos a primeira parte da função que assumem), se é isso que faz a diferença?Nosso plano
map
.flatMap
.for comprehension
.`Mapa de Scala
assinatura do mapa scala:
Mas há uma grande parte faltando quando olhamos para essa assinatura, e é - de onde
A
vem isso ? nosso contêiner é do tipo,A
portanto, é importante examinar essa função no contexto do contêiner -M[A]
. Nosso contêiner pode ser umList
de itens do tipoA
e nossamap
função tem uma função que transforma cada item do tipoA
em tipoB
, então retorna um contêiner do tipoB
(ouM[B]
)Vamos escrever a assinatura do mapa levando em consideração o contêiner:
Observe um fato extremamente importante sobre o mapa - ele se agrupa automaticamente no contêiner de saída que
M[B]
você não tem controle sobre ele. Vamos enfatizar novamente:map
escolhe o contêiner de saída para nós e ele será o mesmo contêiner da fonte em que trabalhamos, então, para oM[A]
contêiner, obtemos o mesmoM
contêiner apenas paraB
M[B]
e nada mais!map
faz essa conteinerização para nós nós apenas damos um mapeamento deA
paraB
e ele colocaria na caixa deM[B]
irá colocá-lo na caixa para nós!Você vê que não especificou como
containerize
o item acabou de especificar como transformar os itens internos. E como temos o mesmo contêinerM
para ambosM[A]
eM[B]
isso significa queM[B]
é o mesmo contêiner, ou seja, se você tiver,List[A]
então você terá umList[B]
e, o mais importante,map
é fazer isso por você!Agora que lidamos com isso,
map
vamos prosseguirflatMap
.FlatMap de Scala
Vamos ver sua assinatura:
Você vê a grande diferença de mapa para
flatMap
em flatMap; estamos fornecendo a função que não apenas converte,A to B
mas também o transforma em contêinerM[B]
.por que nos importamos com quem faz a conteinerização?
Então, por que nos importamos tanto com a função de entrada para mapear / flatMap que faz a containerização
M[B]
ou o próprio mapa faz a containerização para nós?Você vê, no contexto do
for comprehension
que está acontecendo, várias transformações no item fornecido no,for
portanto, estamos dando ao próximo trabalhador em nossa linha de montagem a capacidade de determinar a embalagem. imagine que temos uma linha de montagem, cada trabalhador faz alguma coisa com o produto e apenas o último trabalhador o está embalando em um contêiner! bem-vindo aflatMap
este é o seu propósito, emmap
cada trabalhador, ao terminar de trabalhar no item, também o embala para que você coloque os recipientes sobre os recipientes.O poderoso para a compreensão
Agora vamos dar uma olhada em sua compreensão levando em consideração o que dissemos acima:
O que temos aqui:
mkMatcher
retorna umcontainer
o contêiner contém uma função:String => Boolean
<-
quais elas se traduzem,flatMap
exceto o último.f <- mkMatcher(pat)
é o primeiro emsequence
(pensarassembly line
) tudo o que queremos é pegarf
e passar para o próximo trabalhador na linha de montagem, deixamos o próximo trabalhador em nossa linha de montagem (a próxima função) a capacidade de determinar o que seria embalagem de volta do nosso item é por isso que a última função émap
.O último
g <- mkMatcher(pat2)
usarámap
isso porque é o último na linha de montagem! então ele pode apenas fazer a operação final com amap( g =>
qual sim! retirag
e usa of
que já foi retirado do contêiner pelo,flatMap
portanto, acabamos com o primeiro:mkMatcher (pat) flatMap (f // puxar a função f dar o item ao próximo trabalhador da linha de montagem (você vê que ele tem acesso
f
e não empacota-o de volta, quero dizer, deixe o mapa determinar a embalagem, deixe o próximo trabalhador da linha de montagem determinar o container. mkMatcher (pat2) map (g => f (s) ...)) // como esta é a última função na linha de montagem, vamos usar map e puxar g para fora do contêiner e para a embalagem de volta , suamap
e esta embalagem irão estrangular todo o caminho e ser nossa embalagem ou nosso contêiner, yah!fonte
A justificativa é encadear operações monádicas que fornecem como um benefício o tratamento adequado de erros de "falha rápida".
Na verdade, é muito simples. O
mkMatcher
método retorna umOption
(que é uma Mônada). O resultado damkMatcher
operação monádica é aNone
ou aSome(x)
.Aplicar a função
map
ouflatMap
aNone
sempre retorna aNone
- a função passada como parâmetromap
eflatMap
não é avaliada.Portanto, em seu exemplo, se
mkMatcher(pat)
retornar um Nenhum, o flatMap aplicado a ele retornará aNone
(a segunda operação monádicamkMatcher(pat2)
não será executada) e a finalmap
retornará novamente aNone
. Em outras palavras, se qualquer uma das operações na compreensão de for retornar um Nenhum, você terá um comportamento de falha rápida e o resto das operações não serão executadas.Este é o estilo monádico de tratamento de erros. O estilo imperativo usa exceções, que são basicamente saltos (para uma cláusula catch)
Uma nota final: a
patterns
função é uma maneira típica de "traduzir" um tratamento de erros de estilo imperativo (try
...catch
) para um tratamento de erros de estilo monádico usandoOption
fonte
flatMap
(e nãomap
) é usado para "concatenar" a primeira e a segunda invocação demkMatcher
, mas por quemap
(e nãoflatMap
) é usado para "concatenar" a segundamkMatcher
e oyields
bloco?flatMap
espera que você passe uma função retornando o resultado "empacotado" / levantado na Mônada, enquantomap
fará o empacotamento / levantamento propriamente dito. Durante o encadeamento de chamadas de operações no,for comprehension
você precisa paraflatmap
que as funções passadas como parâmetro possam retornarNone
(você não pode elevar o valor para Nenhum).yield
Espera-se que a última chamada de operação, aquela em, seja executada e retorne um valor; ummap
encadeamento dessa última operação é suficiente e evita ter que elevar o resultado da função para a mônada.Isso pode ser traduzido como:
Execute isto para uma melhor visão de como é expandido
os resultados são:
Isso é semelhante a
flatMap
- fazer um loop através de cada elemento empat
e foreach o elementomap
para cada elemento empat2
fonte
Primeiro,
mkMatcher
retorna uma função cuja assinatura éString => Boolean
, que é um procedimento java regular que acaba de ser executadoPattern.compile(string)
, conforme mostrado napattern
função. Então, olhe para esta linhaA
map
função é aplicada ao resultado depattern
, que éOption[Pattern]
, portanto, op
inp => xxx
é apenas o padrão que você compilou. Assim, dado um padrãop
, uma nova função é construída, que pega uma Strings
e verifica ses
corresponde ao padrão.Observe que a
p
variável está limitada ao padrão compilado. Agora, está claro como uma função com assinaturaString => Boolean
é construída pormkMatcher
.A seguir, vamos verificar a
bothMatch
função em que se baseiamkMatcher
. Para mostrar comobothMathch
funciona, primeiro olhamos para esta parte:Visto que obtivemos uma função com assinatura
String => Boolean
demkMatcher
, que estág
neste contexto,g(s)
é equivalente aPattern.compile(pat2).macher(s).matches
, que retorna se a String s corresponder ao padrãopat2
. Entãof(s)
, que tal, é o mesmo queg(s)
, a única diferença é que, a primeira chamada demkMatcher
usaflatMap
, em vez demap
, Por quê? ComomkMatcher(pat2) map (g => ....)
retornaOption[Boolean]
, você obterá um resultado aninhadoOption[Option[Boolean]]
se usarmap
para ambas as chamadas, não é isso que você deseja.fonte