Каков урожай Scala?

Ответы:

205

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

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

Простой пример (из Скала-Ланга )

/** 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 

в Линк.

Ruby's yieldимеет другой эффект.

Dario
источник
57
Так почему бы мне использовать yield вместо карты? Этот код карты эквивалентен val res = args.map (_. ToUpperCase), верно?
Geo
4
В случае, если вам нравится синтаксис лучше. Кроме того, как указывает Алексей, понимание также обеспечивает хороший синтаксис для доступа к flatMap, фильтрам и foreach.
Натан Шивели-Сандерс
22
Правильно. Если у вас есть простая карта - один генератор без if - я бы, конечно, сказал, что вызов карты более читабелен. Если у вас есть несколько генераторов, зависящих друг от друга, и / или фильтры, вы можете предпочесть выражение для.
Алексей Романов
13
Обратите внимание, что приведенный пример не эквивалентен выражению карты: он такой же. А для понимания переводится на вызовы map, flatMap и filter.
Даниэль С. Собрал
9
Ответ начинается следующим образом: «Он используется в пониманиях последовательности (например, в списках и генераторах Python, где вы также можете использовать yield)». Это ошибочно заставляет думать, что урожайность в Scala аналогична доходности в Python. Это не вариант. В Python yield используется в контексте сопрограмм (или продолжений), тогда как в Scala это не так. Для получения дополнительной информации, пожалуйста, посетите эту ветку: stackoverflow.com/questions/2201882/…
Ричард Гомес
817

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

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

Понимание Scala forявляется синтаксическим сахаром для составления множества операций с картой, flatMapи filter. Или foreach. 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) => {...})

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

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

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

withFilter

В Scala 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

Разница возникает потому, что filterсразу применяется с 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
В scala 2.8 появился новый метод withFilter. for (x <- c; if cond) yield {...} переводится в c.withFilter (x => cond) .map (x => {...}) в scala2.8.
Eastsun
2
@Eastsun Достаточно верно, хотя есть и автоматический запасной вариант. withFilterПредполагается, что он также не является строгим, даже для строгих коллекций, что заслуживает некоторого объяснения Я рассмотрю это ...
Даниэль С. Собрал
2
@Daniel: Эта тема очень хорошо описана в «Программировании в Scala», автором Odersky и соавт. (Я уверен, что вы уже знаете это). +1 за показ.
Ральф
Первые 2 пункта верны с: 1. for(x <- c; y <- x; z <-y) {...}переведено на c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}переведено наc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Доминик
Это for(x <- c; y = ...) yield {...}действительно переведено на c.map(x => (x, ...)).map((x,y) => {...})? Я думаю, что это переведено c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})или я что-то упустил?
простыник
23

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

from ... select ??? 

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

for ... yield ???

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

  • Если ваш тип определяет just map, он разрешает forвыражения, состоящие из одного генератора.
  • Если он определяет flatMapтак же, как map, он допускает forвыражения, состоящие из нескольких генераторов.
  • Если он определяет foreach, он допускает for-loops без выхода (как с одним, так и с несколькими генераторами).
  • Если он определяет filter, он разрешает forвыражения -filter, начинающиеся с выражения if в forвыражении.
Алексей романов
источник
2
@Eldritch Conundrum - что довольно интересно, тот же порядок, в котором изложены исходные спецификации SQL. Где-то на этом пути язык SQL изменил порядок, но имеет смысл сначала описать то, из чего вы тянете, а затем то, что вы ожидаете получить от него.
Джордан Пармер
13

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

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

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

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

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

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

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

Дэниел Уорвикер
источник
1
это должно быть "var doubled = for (n <- original) yield n * 2".
Рассел Ян
12

Ключевым словом yieldв Scala является просто синтаксический сахар, который можно легко заменить на a map, как подробно объяснял Даниэль Собрал .

С другой стороны, yieldабсолютно вводит в заблуждение, если вы ищете генераторы (или продолжения), подобные тем, что есть в Python . Посмотрите эту ветку SO для получения дополнительной информации: Каков предпочтительный способ реализации yield в Scala?

Ричард Гомес
источник
11

Рассмотрим следующее для понимания

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

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

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

С точки зрения математической нотации построителя множеств вышеупомянутое для понимания аналогично

Набор-обозначение

который может быть прочитан как

« Для каждого целого числа я, если оно больше, чем оно 3, оно является членом множества A».

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

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

Марио Галич
источник
2

Выход аналогичен циклу for, в котором есть буфер, который мы не видим, и для каждого приращения он продолжает добавлять следующий элемент в буфер. Когда цикл for завершает работу, он возвращает коллекцию всех полученных значений. Выход можно использовать как простые арифметические операторы или даже в сочетании с массивами. Вот два простых примера для вашего лучшего понимания

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)] = Список ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, а), (3, б), (3, в))

Надеюсь это поможет!!

Манаса Чада
источник
При ответе на этот старый вопрос (более 9 лет назад) полезно указать, чем ваш ответ отличается от всех других уже представленных ответов.
jwvh
Я думал, что разъяснение сомнений важно, а не давать другой ответ, так как даже я также начинающий, который изучает этот язык. Спасибо за предложение.
Манаса Чада
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 )

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

Карта так же гибка, как доходность и наоборот.

dotnetN00b
источник
-3

yield является более гибким, чем map (), см. пример ниже

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 )

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

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

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