Qual é o rendimento de Scala?


Respostas:


205

É 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 fore 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 yieldtem um efeito diferente.


57
Então, por que eu usaria rendimento em vez de mapa? Esse código de mapa é equivalente a val res = args.map (_. ToUpperCase), certo?
Geo

4
Caso você goste mais da sintaxe. Além disso, como alexey aponta, as compreensões também fornecem uma sintaxe agradável para acessar o flatMap, filter e foreach.
Nathan Shively-Sanders

22
Certo. Se você tiver apenas um mapa simples - um gerador sem se - eu certamente diria que chamar o mapa é mais legível. Se você tiver vários geradores dependendo um do outro e / ou filtros, poderá preferir um para expressão.
Alexey Romanov

13
Observe que o exemplo dado não é equivalente à expressão do mapa: é o mesmo. A para compreensão é traduzido para chamadas para mapear, flatMap e filtrar.
2213 Daniel C. Sobral

9
A resposta começa assim: "É usada em compreensões de sequência (como compreensores e geradores de lista do Python, onde você também pode usar yield)." Isso leva a pensar erroneamente que o rendimento em Scala é semelhante ao rendimento em Python. Este não é o caso. Em Python, o rendimento é usado no contexto de corotinas (ou continuações), embora não seja o caso em Scala. Para obter mais esclarecimentos, visite este tópico: stackoverflow.com/questions/2201882/…
Richard Gomes

817

Eu acho que a resposta aceita é ótima, mas parece que muitas pessoas não conseguiram entender alguns pontos fundamentais.

Primeiro, as forcompreensõ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 forcompreensões de Scala são açúcar sintático para composição de múltiplas operações com mapa flatMape filter. Or foreach. O Scala, na verdade, traduz uma forexpressã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:

  1. este

    for(x <- c1; y <- c2; z <-c3) {...}

    é traduzido para

    c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
  2. este

    for(x <- c1; y <- c2; z <- c3) yield {...}

    é traduzido para

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
  3. 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 withFilternão estiver disponível, mas filterestiver. Consulte a seção abaixo para obter mais informações sobre isso.

  4. este

    for(x <- c; y = ...) yield {...}

    é traduzido para

    c.map(x => (x, ...)).map((x,y) => {...})

Quando você olha para forcompreensões muito simples , as alternativas map/ foreachparecem, 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 forcompreensõ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 filtermé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 foundnão tem sentido, como filterjá foi executado.

No caso de Stream, a condição não é aplicada imediatamente. Em vez disso, à medida que cada elemento é solicitado foreach, filtertesta a condição, que permite foreachinfluenciá-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 ifque 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 Listcom 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 filtercomportamento. Como observação, Rangefoi alterado de não-estrito para estrito entre o Scala 2.7 e o Scala 2.8.


2
Há um novo método withFilter no scala 2.8. para (x <- c; se cond), o rendimento {...} é convertido em c.withFilter (x => cond) .map (x => {...}) no scala2.8.
Eastsun

2
@ Eastsun É verdade, mas também há fallback automático. withFilterdeve ser também não-estrito, mesmo para coleções rigorosas, o que merece alguma explicação. Eu vou considerar isso ...
Daniel C. Sobral

2
@ Daniel: Existe um ótimo tratamento desse assunto em "Programming in Scala", de Odersky, et al. (Tenho certeza que você já sabe disso). +1 por mostrá-lo.
Ralph

Os 2 primeiros pontos estão corretos com: 1. 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 => {...})))
Dominik

Isso é 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?
prostynick

23

Sim, como Earwicker disse, é praticamente o equivalente ao LINQ selecte 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 fornão funcionam apenas com sequências, mas com qualquer tipo que define determinados métodos, assim como o LINQ:

  • Se o seu tipo define apenas map, ele permite for-expressions consistindo em um único gerador.
  • Se definir flatMap, bem comomap , permitirá forexpressões que consistem em vários geradores.
  • Se define foreach, permitefor -loops sem rendimento (ambos com geradores únicos e múltiplos).
  • Se definido filter, ele permite forexpressões -filter começando com um if na forexpressão.

2
@Eldritch Conundrum - O que é interessante é a mesma ordem em que a especificação original do SQL descreve. Em algum lugar ao longo do caminho, a linguagem SQL inverteu a ordem, mas faz todo sentido descrever primeiro o que você está buscando, seguido pelo que você espera obter dele.
Jordan Parmer

13

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).


1
deve ser "var duplicado = para (n <- original) rendimento n * 2".
Russel Yang


11

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) ie adicione-o à listaA ."

Em termos de notação matemática de construtores de conjuntos , a compreensão acima é análoga a

notação de set

que pode ser lido como

" Para cada número inteiro Eu, se for maior que 3, então é um membro do conjuntoUMA ."

ou alternativamente como

" UMAé o conjunto de todos os números inteiros Eu, de modo que cada um Eué maior que 3."


2

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!!


Ao responder a uma pergunta tão antiga (mais de 9 anos atrás), é útil ressaltar como sua resposta é diferente de todas as outras respostas já enviadas.
jwvh

Eu pensei que esclarecer a dúvida é importante e não dar uma resposta diferente, pois mesmo eu também sou um iniciante que está aprendendo esse idioma. Obrigado pela sugestão.
Manasa Chada

0
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.


-3

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.


4
Essa comparação está errada. Você está comparando duas coisas diferentes. A expressão yield não está de maneira alguma fazendo a mesma coisa que a expressão no mapa. Além disso, ele não mostra a "flexibilidade" do rendimento em comparação com o mapa.
DotnetN00b
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.