Jaka jest wydajność Scali?

Odpowiedzi:

205

Jest używany w interpretacjach sekwencyjnych (takich jak listy-generatory Pythona i generatory, z których możesz także korzystać yield).

Jest stosowany w połączeniu z fori zapisuje nowy element w wynikowej sekwencji.

Prosty przykład (ze 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)
  }
}

Odpowiednie wyrażenie w F # byłoby

[ for a in args -> a.toUpperCase ]

lub

from a in args select a.toUpperCase 

w Linq.

Ruby yieldma inny efekt.

Dario
źródło
57
Dlaczego więc miałbym używać plonu zamiast mapy? Ten kod mapy jest równoważny val res = args.map (_. ToUpperCase), prawda?
Geo
4
Jeśli bardziej podoba ci się składnia. Ponadto, jak wskazuje Alexey, rozumienia również zapewniają ładną składnię dostępu do flatMap, filtrowania i foreach.
Nathan Shively-Sanders
22
Dobrze. Jeśli masz tylko prostą mapę - jeden generator bez, jeśli nie - z pewnością powiedziałbym, że wywołanie mapy jest bardziej czytelne. Jeśli masz kilka generatorów zależnych od siebie i / lub filtrów, możesz preferować wyrażenie do wyrażenia.
Alexey Romanov
13
Pamiętaj, że podany przykład nie jest równoważny wyrażeniu mapy: jest taki sam. Zrozumienie jest tłumaczone na wywołania map, flatMap i filter.
Daniel C. Sobral
9
Odpowiedź zaczyna się w ten sposób: „Jest używana w interpretacjach sekwencyjnych (takich jak interpretacje list Pythona i generatory, w których można również użyć wydajności)”. To błędnie prowadzi do myślenia, że ​​wydajność w Scali jest podobna do wydajności w Pythonie. Nie o to chodzi. W Pythonie plon jest używany w kontekście coroutines (lub kontynuacji), podczas gdy w Scali tak nie jest. Aby uzyskać więcej wyjaśnień, odwiedź ten wątek: stackoverflow.com/questions/2201882/...
Richard Gomes
817

Uważam, że zaakceptowana odpowiedź jest świetna, ale wydaje się, że wielu ludziom nie udało się zrozumieć pewnych podstawowych kwestii.

Po pierwsze, forrozumienia Scali są równoważne donotacji Haskella i nie są niczym więcej jak cukrem syntaktycznym do kompozycji wielu operacji monadycznych. Ponieważ to stwierdzenie najprawdopodobniej nie pomoże nikomu, kto potrzebuje pomocy, spróbujmy jeszcze raz… :-)

Zrozumienia Scali forto cukier składniowy do kompozycji wielu operacji z mapą flatMapi filter. Lub foreach. Scala faktycznie tłumaczy for-wyrażenie na wywołania tych metod, więc każda klasa, która je udostępnia, lub ich podzbiór, może być używana ze zrozumieniem.

Najpierw porozmawiajmy o tłumaczeniach. Istnieją bardzo proste zasady:

  1. To

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

    jest przetłumaczone na

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

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

    jest przetłumaczone na

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
  3. To

    for(x <- c; if cond) yield {...}

    jest przetłumaczony na Scala 2.7 na

    c.filter(x => cond).map(x => {...})

    lub, na Scala 2.8, do

    c.withFilter(x => cond).map(x => {...})

    z rezerwą na poprzednie, jeśli metoda withFilternie jest dostępna, ale filterjest. Więcej informacji na ten temat znajduje się w sekcji poniżej.

  4. To

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

    jest przetłumaczone na

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

Kiedy patrzysz na bardzo proste forpojęcia, alternatywy map/ foreachwyglądają rzeczywiście lepiej. Gdy jednak zaczniesz je komponować, możesz łatwo zgubić się w poziomach nawiasów i zagnieżdżania. Kiedy tak się dzieje, forrozumienie jest zwykle znacznie wyraźniejsze.

Pokażę jeden prosty przykład i celowo pominę wszelkie wyjaśnienia. Możesz zdecydować, która składnia była łatwiejsza do zrozumienia.

l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))

lub

for {
  sl <- l
  el <- sl
  if el > 0
} yield el.toString.length

withFilter

Scala 2.8 wprowadziła metodę o nazwie withFilter, której główną różnicą jest to, że zamiast zwracać nową, przefiltrowaną kolekcję, filtruje na żądanie. filterMetoda ma swoje zachowanie zdefiniowane w oparciu o surowości kolekcji. Aby to lepiej zrozumieć, spójrzmy na Scala 2.7 z List(ścisłe) i Stream(nie ścisłe):

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

Różnica dzieje się, ponieważ filterjest natychmiast stosowana z List, zwracając listę szans - ponieważ foundjest false. Dopiero wtedy foreachjest wykonywana, ale do tego czasu zmiana foundjest bez znaczenia, tak jak filterjuż została wykonana.

W przypadku Streamwarunek nie jest natychmiast stosowany. Zamiast tego, zgodnie z żądaniem każdego elementu foreach, filtertestuje warunek, który pozwala foreachna jego wpływ found. Żeby było jasne, oto kod równoważny dla zrozumienia:

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)

Powodowało to wiele problemów, ponieważ ludzie spodziewali się, że ifbędą one uważane za dostępne na żądanie, zamiast wcześniejszego zastosowania do całej kolekcji.

Wprowadzono Scala 2.8 withFilter, która zawsze nie jest ścisła, bez względu na surowość kolekcji. Poniższy przykład pokazuje Listobie metody w Scali 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

Daje to wynik, którego większość ludzi oczekuje, bez zmiany filterzachowania. Na marginesie, Rangezmieniono z nieprecyzyjnego na ścisłe między Scala 2.7 i Scala 2.8.

Daniel C. Sobral
źródło
2
Istnieje nowa metoda z filtrem w scala 2.8. for (x <- c; if cond) return {...} jest tłumaczone na c.withFilter (x => cond) .map (x => {...}) w scala2.8.
Eastsun
2
@Eastun To prawda, ale istnieje również automatyczny powrót do poprzedniej wersji. withFilterma być również surowe, nawet w przypadku ścisłych kolekcji, które zasługują na wyjaśnienie. Rozważę to ...
Daniel C. Sobral
2
@Daniel: Świetnie potraktowano ten temat w „Programming in Scala”, autor: Odersky i in. (Jestem pewien, że już to wiesz). +1 za pokazanie.
Ralph,
Pierwsze 2 punkty są poprawne z: 1. for(x <- c; y <- x; z <-y) {...}przetłumaczono na c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}przetłumaczono nac.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik
Czy to for(x <- c; y = ...) yield {...}naprawdę jest przetłumaczone na c.map(x => (x, ...)).map((x,y) => {...})? Myślę, że to jest przetłumaczone na, c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})czy coś mi brakuje?
prostynick
23

Tak, jak powiedział Earwicker, jest to prawie odpowiednik LINQ selecti ma bardzo niewiele wspólnego z Ruby i Pythonem yield. Zasadniczo, gdzie w C # napiszesz

from ... select ??? 

w Scali masz zamiast tego

for ... yield ???

Ważne jest również, aby zrozumieć, że for-zrozumienia nie działają tylko z sekwencjami, ale z każdym typem, który definiuje pewne metody, tak jak LINQ:

  • Jeśli twój typ definiuje tylko map, pozwala for-wyrażenia składające się z jednego generatora.
  • Jeśli definiuje flatMaprównież map, pozwala na for-wyrażenia składające się z kilku generatorów.
  • Jeśli to zdefiniuje, foreachpozwala for-laops bez wydajności (zarówno z jednym, jak i wieloma generatorami).
  • Jeśli to zdefiniuje, filterumożliwia forwyrażenia -filter zaczynające się if od forwyrażenia w.
Aleksiej Romanow
źródło
2
@Eldritch Conundrum - Co ciekawe, ta sama kolejność, w jakiej zarysowana jest oryginalna specyfikacja SQL. Gdzieś po drodze język SQL odwrócił kolejność, ale całkowicie uzasadnione jest najpierw opisanie tego, co wyciągasz, a następnie tego, czego oczekujesz.
Jordan Parmer
13

O ile nie uzyskasz lepszej odpowiedzi od użytkownika Scali (którego nie jestem), oto moje zrozumienie.

Pojawia się tylko jako część wyrażenia rozpoczynającego się od for, które określa, jak wygenerować nową listę z istniejącej listy.

Coś jak:

var doubled = for (n <- original) yield n * 2

Jest więc jeden element wyjściowy dla każdego wejścia (chociaż uważam, że istnieje sposób na usunięcie duplikatów).

Jest to całkiem odmienne od „kontynuacji imperatywnych” włączonych przez funkcję fed w innych językach, gdzie zapewnia sposób generowania listy o dowolnej długości, z jakiegoś imperatywnego kodu o prawie dowolnej strukturze.

(Jeśli znasz C #, jest bliżej operatora LINQ select niż jest yield return).

Daniel Earwicker
źródło
1
powinno to być „var double = = (n <- oryginał) wydajność n * 2”.
Russel Yang,
11

Rozważ następujące zrozumienie

val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i

Pomocne może być przeczytanie go na głos w następujący sposób

Dla każdej liczby całkowitej i, jeśli jest ona większa niż3 , wydaj (produkuj) ii dodaj ją do listy A”.

Pod względem matematycznym notacji konstruktora zestawów powyższe rozumienie jest analogiczne do

zestaw notacji

który można odczytać jako

Dla każdej liczby całkowitej ja, jeśli jest ona większa niż 3), to należy ona do zbioru ZA”.

lub alternatywnie jako

ZAjest zbiorem wszystkich liczb całkowitych ja, z których każda jajest większa niż 3)”.

Mario Galic
źródło
2

Wydajność jest podobna do pętli for, która ma bufor, którego nie widzimy, i dla każdego przyrostu dodaje kolejny element do bufora. Gdy pętla for zakończy działanie, zwróci kolekcję wszystkich uzyskanych wartości. Wydajność może być używana jako proste operatory arytmetyczne lub nawet w połączeniu z tablicami. Oto dwa proste przykłady lepszego zrozumienia

scala>for (i <- 1 to 5) yield i * 3

res: scala.collection.immutable.IndexedSeq [Int] = Vector (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)] = List ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))

Mam nadzieję że to pomoże!!

Manasa Chada
źródło
Odpowiadając na tak stare pytanie (ponad 9 lat temu), warto wskazać, w jaki sposób Twoja odpowiedź różni się od wszystkich pozostałych odpowiedzi już przesłanych.
jwvh
Pomyślałem, że wyjaśnienie wątpliwości jest ważne i nie udzielanie innej odpowiedzi, ponieważ nawet ja jestem również początkującym, który uczy się tego języka. Dzieki za sugestie.
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 )

Te dwa fragmenty kodu są równoważne.

val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )

println( res3 ) 
println( res4 )

Te dwa fragmenty kodu są również równoważne.

Mapa jest tak elastyczna jak plon i odwrotnie.

dotnetN00b
źródło
-3

wydajność jest bardziej elastyczna niż map (), patrz przykład poniżej

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 )

wydajność wydrukuje wynik taki jak: Lista (5, 6), co jest dobre

podczas gdy map () zwróci wynik: List (false, false, true, true, true), co prawdopodobnie nie jest tym, co zamierzasz.

Michael Peng
źródło
4
To porównanie jest złe. Porównujesz dwie różne rzeczy. Wydajność wyrażenia w żaden sposób nie robi tego samego, co wyrażenie na mapie. Nie pokazuje też wcale „elastyczności” plonu w porównaniu z mapą.
dotnetN00b