Respostas:
É usado em compreensões de sequência (como compreensão de lista e geradores do Python, onde você também pode usar yield
).
É aplicado em combinação com for
e grava um novo elemento na sequência resultante.
Exemplo simples (de scala-lang )
/** Turn command line arguments to uppercase */
object Main {
def main(args: Array[String]) {
val res = for (a <- args) yield a.toUpperCase
println("Arguments: " + res.toString)
}
}
A expressão correspondente em F # seria
[ for a in args -> a.toUpperCase ]
ou
from a in args select a.toUpperCase
em Linq.
Ruby yield
tem um efeito diferente.
Eu acho que a resposta aceita é ótima, mas parece que muitas pessoas não conseguiram entender alguns pontos fundamentais.
Primeiro, as for
compreensões de Scala são equivalentes às de Haskelldo
notação , e nada mais é do que um açúcar sintático para a composição de múltiplas operações monádicas. Como esta declaração provavelmente não ajudará quem precisa de ajuda, vamos tentar novamente ... :-)
As for
compreensões de Scala são açúcar sintático para composição de múltiplas operações com mapa flatMap
e filter
. Or foreach
. O Scala, na verdade, traduz uma for
expressão-em chamadas para esses métodos, para que qualquer classe que os forneça, ou um subconjunto deles, possa ser usada para compreensão.
Primeiro, vamos falar sobre as traduções. Existem regras muito simples:
este
for(x <- c1; y <- c2; z <-c3) {...}
é traduzido para
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
este
for(x <- c1; y <- c2; z <- c3) yield {...}
é traduzido para
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
este
for(x <- c; if cond) yield {...}
é traduzido no Scala 2.7 para
c.filter(x => cond).map(x => {...})
ou, no Scala 2.8, em
c.withFilter(x => cond).map(x => {...})
com um fallback para o antigo se o método withFilter
não estiver disponível, mas filter
estiver. Consulte a seção abaixo para obter mais informações sobre isso.
este
for(x <- c; y = ...) yield {...}
é traduzido para
c.map(x => (x, ...)).map((x,y) => {...})
Quando você olha para for
compreensões muito simples , as alternativas map
/ foreach
parecem, de fato, melhores. Depois de começar a compor, você pode facilmente se perder nos níveis de parênteses e aninhamento. Quando isso acontece, as for
compreensões são geralmente muito mais claras.
Vou mostrar um exemplo simples e omitir intencionalmente qualquer explicação. Você pode decidir qual sintaxe foi mais fácil de entender.
l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))
ou
for {
sl <- l
el <- sl
if el > 0
} yield el.toString.length
withFilter
O Scala 2.8 introduziu um método chamado withFilter
, cuja principal diferença é que, em vez de retornar uma nova coleção filtrada, ele filtra sob demanda. O filter
método tem seu comportamento definido com base no rigor da coleção. Para entender isso melhor, vamos dar uma olhada em alguns Scala 2.7 com List
(strict) e Stream
(non-strict):
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
A diferença acontece porque filter
é aplicada imediatamente com List
, retornando uma lista de probabilidades - desde que found
é false
. Só então foreach
é executado, mas, a essa altura, a mudança found
não tem sentido, como filter
já foi executado.
No caso de Stream
, a condição não é aplicada imediatamente. Em vez disso, à medida que cada elemento é solicitado foreach
, filter
testa a condição, que permite foreach
influenciá-la found
. Apenas para esclarecer, eis o código de compreensão equivalente:
for (x <- List.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
for (x <- Stream.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
Isso causou muitos problemas, porque as pessoas esperavam if
que isso fosse considerado sob demanda, em vez de ser aplicado a toda a coleção com antecedência.
Introdução do Scala 2.8 withFilter
, que é sempre não rigorosa, independentemente do rigor da coleção. O exemplo a seguir mostra List
com os dois métodos no Scala 2.8:
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
Isso produz o resultado que a maioria das pessoas espera, sem alterar o filter
comportamento. Como observação, Range
foi alterado de não-estrito para estrito entre o Scala 2.7 e o Scala 2.8.
withFilter
deve ser também não-estrito, mesmo para coleções rigorosas, o que merece alguma explicação. Eu vou considerar isso ...
for(x <- c; y <- x; z <-y) {...}
é traduzido em c.foreach(x => x.foreach(y => y.foreach(z => {...})))
2. for(x <- c; y <- x; z <- y) yield {...}
é traduzido emc.flatMap(x => x.flatMap(y => y.map(z => {...})))
for(x <- c; y = ...) yield {...}
realmente traduzido c.map(x => (x, ...)).map((x,y) => {...})
? Eu acho que está traduzido c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})
ou estou faltando alguma coisa?
Sim, como Earwicker disse, é praticamente o equivalente ao LINQ select
e tem muito pouco a ver com Ruby e Python yield
. Basicamente, onde em C # você escreveria
from ... select ???
em Scala você tem
for ... yield ???
Também é importante entender que as compreensões for
não funcionam apenas com sequências, mas com qualquer tipo que define determinados métodos, assim como o LINQ:
map
, ele permite for
-expressions consistindo em um único gerador.flatMap
, bem comomap
, permitirá for
expressões que consistem em vários geradores.foreach
, permitefor
-loops sem rendimento (ambos com geradores únicos e múltiplos).filter
, ele permite for
expressões -filter começando com um if
na for
expressão.A menos que você obtenha uma resposta melhor de um usuário do Scala (o que eu não sou), aqui está o meu entendimento.
Ele aparece apenas como parte de uma expressão que começa com for
, que indica como gerar uma nova lista a partir de uma lista existente.
Algo como:
var doubled = for (n <- original) yield n * 2
Portanto, há um item de saída para cada entrada (embora eu acredite que haja uma maneira de eliminar duplicatas).
Isso é bem diferente das "continuações imperativas" ativadas pelo rendimento em outros idiomas, onde fornece uma maneira de gerar uma lista de qualquer tamanho, a partir de algum código imperativo com quase qualquer estrutura.
(Se você conhece C #, é mais próximo do select
operador do LINQ do que yield return
).
A palavra-chave yield
em Scala é simplesmente açúcar sintático que pode ser facilmente substituído por a map
, como Daniel Sobral já explicou em detalhes.
Por outro lado, yield
é absolutamente enganoso se você estiver procurando por geradores (ou continuações) semelhantes aos do Python . Veja este tópico do SO para obter mais informações: Qual é a maneira preferida de implementar 'yield' no Scala?
Considere o seguinte para compreensão
val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i
Pode ser útil ler em voz alta da seguinte maneira
" Para cada número inteiro i
, se for maior que 3
, então produza (produza) i
e adicione-o à listaA
."
Em termos de notação matemática de construtores de conjuntos , a compreensão acima é análoga a
que pode ser lido como
" Para cada número inteiro , se for maior que , então é um membro do conjunto ."
ou alternativamente como
" é o conjunto de todos os números inteiros , de modo que cada um é maior que ."
O rendimento é semelhante ao loop for, que possui um buffer que não podemos ver e, para cada incremento, ele continua adicionando o próximo item ao buffer. Quando o loop for termina a execução, ele retorna a coleção de todos os valores produzidos. O rendimento pode ser usado como operadores aritméticos simples ou mesmo em combinação com matrizes. Aqui estão dois exemplos simples para sua melhor compreensão
scala>for (i <- 1 to 5) yield i * 3
res: scala.collection.immutable.IndexedSeq [Int] = Vetor (3, 6, 9, 12, 15)
scala> val nums = Seq(1,2,3)
nums: Seq[Int] = List(1, 2, 3)
scala> val letters = Seq('a', 'b', 'c')
letters: Seq[Char] = List(a, b, c)
scala> val res = for {
| n <- nums
| c <- letters
| } yield (n, c)
res: Seq [(Int, Char)] = Lista ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))
Espero que isto ajude!!
val aList = List( 1,2,3,4,5 )
val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.filter(_ > 3).map(_ + 1)
println( res3 )
println( res4 )
Esses dois pedaços de código são equivalentes.
val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )
println( res3 )
println( res4 )
Esses dois pedaços de código também são equivalentes.
O mapa é tão flexível quanto o rendimento e vice-versa.
rendimento é mais flexível que map (), veja o exemplo abaixo
val aList = List( 1,2,3,4,5 )
val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.map( _+ 1 > 3 )
println( res3 )
println( res4 )
o rendimento imprimirá resultados como: List (5, 6), o que é bom
enquanto map () retornará resultado como: List (false, false, true, true, true), o que provavelmente não é o que você pretende.