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

289

Я понимаю доходность Ruby и Python. Что делает выход Scala?

Теги:
functional-programming
yield

9 ответов

184
Лучший ответ

Он используется в последовательности понимания (например, 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 yield имеет другой эффект.

  • 55
    Так почему бы мне использовать yield вместо карты? Этот код карты эквивалентен val res = args.map (_. ToUpperCase), верно?
  • 4
    В случае, если вам нравится синтаксис лучше. Кроме того, как указывает Алексей, понимание также обеспечивает хороший синтаксис для доступа к flatMap, фильтрам и foreach.
Показать ещё 7 комментариев
738

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

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

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

Во-первых, позвольте поговорить о переводах. Существуют очень простые правила:

  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.
  • 2
    @Eastsun Достаточно верно, хотя есть и автоматический запасной вариант. withFilter должен быть нестрогим, даже для строгих коллекций, что заслуживает некоторого объяснения. Я рассмотрю это ...
Показать ещё 4 комментария
24

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

from ... select ??? 

в Scala у вас вместо этого

for ... yield ???

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

  • Если ваш тип определяет только map, он позволяет for -выражения, состоящие из одиночный генератор.
  • Если он определяет flatMap, а также map, он позволяет for -выражения, состоящие нескольких генераторов.
  • Если он определяет foreach, он позволяет for -loops без yield (как с одним, так и с несколькими генераторами).
  • Если он определяет filter, он позволяет выражать выражения for -filter, начиная с if в выражении for.
  • 1
    в LINQ C # правильный порядок "от ... выберите ???"
  • 2
    @Eldritch Conundrum - что довольно интересно, тот же порядок, в котором изложены исходные спецификации SQL. Где-то на этом пути язык SQL изменил порядок, но имеет смысл сначала описать то, из чего вы тянете, а затем то, что вы ожидаете получить от него.
12

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

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

12

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

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

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

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

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

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

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

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

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

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

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

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

В терминах обозначения математического набора-построек вышеприведенное для понимания аналогично

[IMG_OUR_ID=6057.latex?A%20%3D%20%5Cleft%20%5C%7B%20i%20%5Cin%20%5Cmathbb%7BZ%7D%20%3A%20i%3E3%20%5Cright%20%5C%7D]

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

" Для каждого целого [IMG_OUR_ID=6058.latex?i], если он больше, чем [IMG_OUR_ID=6059.latex?3], то он является членом множества [IMG_OUR_ID=6060.latex?A]".

или, альтернативно, как

"[IMG_OUR_ID=6060.latex?A] - это набор всех целых чисел [IMG_OUR_ID=6058.latex?i], так что каждый [IMG_OUR_ID=6058.latex?i] больше, чем [IMG_OUR_ID=6059.latex?3]".

0

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

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

res: scala.collection.immutable.IndexedSeq [Int] = Вектор (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, а), (3, б), (3, с))

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

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

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

Карта является такой же гибкой, как и выход, и наоборот.

-2

выход более гибкий, чем 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 )
Результат

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

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

  • 4
    Это сравнение неверно. Вы сравниваете две разные вещи. Выражение в yield никоим образом не совпадает с выражением на карте. Кроме того, он не показывает «гибкость» урожайности по сравнению с картой вообще.
  • 0
    yield al + 1> 3 сделает то же самое

Ещё вопросы

Сообщество Overcoder
Наверх
Меню