Odpowiedzi:
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 for
i 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 yield
ma inny efekt.
Uważam, że zaakceptowana odpowiedź jest świetna, ale wydaje się, że wielu ludziom nie udało się zrozumieć pewnych podstawowych kwestii.
Po pierwsze, for
rozumienia Scali są równoważne do
notacji 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 for
to cukier składniowy do kompozycji wielu operacji z mapą flatMap
i 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:
To
for(x <- c1; y <- c2; z <-c3) {...}
jest przetłumaczone na
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
To
for(x <- c1; y <- c2; z <- c3) yield {...}
jest przetłumaczone na
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
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 withFilter
nie jest dostępna, ale filter
jest. Więcej informacji na ten temat znajduje się w sekcji poniżej.
To
for(x <- c; y = ...) yield {...}
jest przetłumaczone na
c.map(x => (x, ...)).map((x,y) => {...})
Kiedy patrzysz na bardzo proste for
pojęcia, alternatywy map
/ foreach
wyglą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, for
rozumienie 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. filter
Metoda 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ż filter
jest natychmiast stosowana z List
, zwracając listę szans - ponieważ found
jest false
. Dopiero wtedy foreach
jest wykonywana, ale do tego czasu zmiana found
jest bez znaczenia, tak jak filter
już została wykonana.
W przypadku Stream
warunek nie jest natychmiast stosowany. Zamiast tego, zgodnie z żądaniem każdego elementu foreach
, filter
testuje warunek, który pozwala foreach
na 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 if
bę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 List
obie 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 filter
zachowania. Na marginesie, Range
zmieniono z nieprecyzyjnego na ścisłe między Scala 2.7 i Scala 2.8.
withFilter
ma być również surowe, nawet w przypadku ścisłych kolekcji, które zasługują na wyjaśnienie. Rozważę to ...
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 => {...})))
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?
Tak, jak powiedział Earwicker, jest to prawie odpowiednik LINQ select
i 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:
map
, pozwala for
-wyrażenia składające się z jednego generatora.flatMap
również map
, pozwala na for
-wyrażenia składające się z kilku generatorów.foreach
pozwala for
-laops bez wydajności (zarówno z jednym, jak i wieloma generatorami).filter
umożliwia for
wyrażenia -filter zaczynające się if
od for
wyrażenia w.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
).
Słowem kluczowym yield
w Scali jest po prostu cukier składniowy, który można łatwo zastąpić przez map
, jak szczegółowo wyjaśnił Daniel Sobral .
Z drugiej strony yield
jest całkowicie mylące, jeśli szukasz generatorów (lub kontynuacji) podobnych do tych w Pythonie . Zobacz ten wątek SO, aby uzyskać więcej informacji: Jaki jest preferowany sposób implementacji „fedrunku” w Scali?
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) i
i dodaj ją do listy A
”.
Pod względem matematycznym notacji konstruktora zestawów powyższe rozumienie jest analogiczne do
który można odczytać jako
„ Dla każdej liczby całkowitej , jeśli jest ona większa niż , to należy ona do zbioru ”.
lub alternatywnie jako
„ jest zbiorem wszystkich liczb całkowitych , z których każda jest większa niż ”.
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!!
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.
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.