2009-06-27 2 views

ответ

178

Используется в sequence comprehensions (например, для составления списков и генераторов Python, где вы также можете использовать yield).

Он применяется в сочетании с for и записывает новый элемент в результирующую последовательность.

Простой пример (из 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) 
    } 
} 

Соответствующее выражение в F # будет

[ for a in args -> a.toUpperCase ] 

или

from a in args select a.toUpperCase 

в Linq.

Ruby's yield имеет значение.

+49

Итак, почему я должен использовать доход вместо карты? Этот код карты эквивалентен val res = args.map (_. ToUpperCase), правильно? – Geo

+4

Если вам нравится синтаксис лучше. Кроме того, как отмечает alexey, понимание также обеспечивает хороший синтаксис для доступа к flatMap, filter и foreach. –

+2

Я бы предпочел синтаксис карты args {_ toUpperCase} лично, поскольку он «чувствует» намного больше OO. Похоже, что в соответствии с целью Scala, предоставляемой через библиотечную поддержку, доступно только на других языках через специальные конструкции и ключевые слова. –

10

Если вы не получите лучшего ответа от пользователя Scala (которого я не знаю), вот мое понимание.

Он появляется только как часть выражения, начинающегося с for, в котором говорится, как сгенерировать новый список из существующего списка.

Что-то вроде:

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

Таким образом, есть один выходной элемент для каждого входа (хотя я считаю, что есть способ отбрасывания дубликатов).

Это сильно отличается от «императивных продолжений», разрешенных выходом на других языках, где он предоставляет способ генерации списка любой длины из некоторого императивного кода с почти любой структурой.

(Если вы знакомы с C#, это ближе к LINQ'sselect операторам, чем к yield return).

+1

это должно быть «var doubled = for (n <- original) yield n * 2 ". –

+0

Это тот, который я могу легко и ясно понять. –

21

Да, как сказал Эрвиккер, это в значительной степени эквивалентно LINQ и имеет очень мало общего с Ruby's и Python's yield. В принципе, где в C# можно было бы написать

from ... select ??? 

в Scala у вас есть вместо

for ... yield ??? 

Это также важно понимать, что for -comprehensions не просто работать с последовательностями, но и с любым типом, который определяет определенные методы, так же, как LINQ:

  • Если ваш тип определяет только map, это позволяет for -expressions, состоящий из одиночный генератор.
  • Если он определяет flatMap, а также map, он позволяет получить for -выражения, состоящие из нескольких генераторов.
  • Если он определяет foreach, он позволяет for -loops без выхода (как с одним, так и с несколькими генераторами).
  • Если он определяет filter, он позволяет выражать for -фильтров, начиная с if в выражении for.
+1

в LINQ C#, правильный порядок -« из ... select ??? » –

+2

@ Eldritch Conundrum - Что интересно, это тот же порядок, в котором исходные спецификации спецификаций SQL. Где-то по пути язык SQL перевернул порядок, но имеет смысл сначала описать то, что вы вытаскиваете, за которым следует то, что вы ожидаете от него. –

758

Я думаю, что принятый ответ велик, но, похоже, многие люди не смогли понять некоторые фундаментальные моменты.

Во-первых, «понимание» Scala эквивалентно нотации Haskell «do», и это не что иное, как синтаксический сахар для композиции нескольких монадических операций. Поскольку это заявление, скорее всего, не поможет никому, кто нуждается в помощи, давайте попробуем еще раз ... :-)

«Сказочные» решения Scala - это синтаксический сахар для составления нескольких операций с картой, плоской матрицей и фильтром. Или проследить. Scala фактически переводит выражение for в вызовы этих методов, поэтому любой класс, предоставляющий их, или их подмножество, может использоваться для понимания.

Прежде всего, давайте поговорим о переводах. Есть очень простые правила:

1) Это

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

переводится в

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

2) Этот

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

переводится в

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

3) Эта

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

переводится на Scala 2.7 в

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

или, Scala 2.8, в

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

с запасным вариантом в прежнем, если метод withFilter является не доступно, но filter есть. Подробнее см. Ниже.

4) Это

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

переводится в

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

Когда вы смотрите на очень простой для постижений, альтернативы карта/Foreach выглядят, действительно, лучше. Однако, если вы начнете их составлять, вы можете легко потеряться в скобках и уровнях вложенности. Когда это случается, для понимания, как правило, гораздо яснее.

Я покажу один простой пример и намеренно опускаю любые объяснения. Вы можете решить, какой синтаксис легче понять.

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

или

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

РЕДАКТИРОВАТЬ

Скала 2.8 введен метод, называемый withFilter, основная разница в том, что, вместо возврата нового, фильтруют, сбор, он фильтрует на- спрос. Метод filter имеет свое поведение, основанное на строгости коллекции. Чтобы лучше это понять, давайте посмотрим на некоторые Scala 2.7 с List (строгой) и Stream (нестрогого):

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 

Разница происходит потому, что фильтр будет немедленно применен с List, возвращая список шансов - - с found - false. Только тогда выполняется foreach, но к этому времени изменение found бессмысленно, так как filter уже выполнено.

В случае Stream это условие не применяется немедленно. Вместо этого, поскольку каждый элемент запрашивается foreach, filter проверяет состояние, которое позволяет foreach влиять на него через found. Просто чтобы понять, вот код эквивалента постижения:

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) 

Это вызвало много проблем, потому что люди ожидали if рассматривать по требованию, вместо того, применяется ко всей коллекции заранее.

Scala 2.8 представил withFilter, который является всегда нестрогим, независимо от его строгости. Следующий пример показывает List с обоими методами на 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 

Это дает результат большинство людей ожидают, без изменения, как filter ведет себя. В качестве побочного примечания, Range был изменен с нестрого на строго между Scala 2.7 и Scala 2.8.

+2

Существует новый метод withFilter в scala 2.8. for (x <- c; if cond) yield {...} перевести на c.withFilter (x => cond) .map (x => {...}) в scala2.8. – Eastsun

+1

@ Eastsun Правда, хотя есть и автоматический откат. 'withFilter' также должен быть нестрогим, даже для строгих коллекций, который заслуживает некоторого объяснения. Я рассмотрю это ... –

+35

Один из лучших ответов, которые я видел на SO. +1 – Allyn

10

Ключевое слово yield в Scala просто синтаксический сахар, который может быть легко заменен map как Daniel Sobral already explained подробно.

С другой стороны, yield абсолютно вводит в заблуждении, если вы ищете генераторы (или продолжения), подобные those in Python. Смотрите эту SO нить для получения дополнительной информации: What is the preferred way to implement 'yield' in Scala?

+0

Хорошая точка ...! –

-2

выхода является более гибким, чем карты(), смотрите пример ниже

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) 

выхода напечатают результат как: Список (5, 6), что хорошо

, в то время как map() вернет результат, например: List (false, false, true, true, true), что, вероятно, не то, что вы намереваетесь.

+4

Это сравнение неверно. Вы сравниваете две разные вещи. Выражение в доходности никоим образом не делает то же самое, что выражение на карте. Кроме того, он не показывает «гибкость» урожая по сравнению с картой вообще. – dotnetN00b

+0

выход al + 1> 3 сделал бы тот же трюк – philix

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) 

Эти две части кода являются эквивалентными.

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

println(res3) 
println(res4) 

Эти два фрагмента кода также эквивалентны.

Карта такая же гибкая, как и урожайность, и наоборот.

4

Рассмотрим следующий for-comprehension

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

Это может быть полезно прочитать его вслух следующим

"Для каждого целого i, если это больше, чем 3, то выход (произвести) i и добавить его в список A. "

С точки зрения математической set-builder notation, выше для-понимания аналогично

set-notation

, которые могут быть прочитаны, как

"Для каждое целое число i, , если это превышает 3, тогда это является членом из набора A. "

или в качестве альтернативы, как

«A есть множество всех целых чисел i, таким образом, что каждая i больше, чем 3

Смежные вопросы