Каков урожай Scala?
Я понимаю доходность Ruby и Python. Что делает урожай Scala?
11 ответов
Он используется в понимании последовательностей (например, в списках и генераторах 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
в Линк.
Руби yield
имеет другой эффект.
Я думаю, что принятый ответ - это здорово, но, похоже, многим не удалось понять некоторые фундаментальные моменты.
Во-первых, Скала for
Понимания эквивалентны Хаскеллу do
нотации, и это не более чем синтаксический сахар для составления нескольких монадических операций. Поскольку это утверждение, скорее всего, не поможет никому, кто нуждается в помощи, давайте попробуем еще раз…:-)
в Scala for
понимание является синтаксическим сахаром для составления множества операций с картой, flatMap
а также filter
, Или же foreach
, Скала на самом деле переводит for
-выражение в вызовах этих методов, так что любой класс, предоставляющий их, или их подмножество, может быть использован для понимания.
Сначала поговорим о переводах. Есть очень простые правила:
это
for(x <- c1; y <- c2; z <-c3) {...}
переводится на
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
это
for(x <- c1; y <- c2; z <- c3) yield {...}
переводится на
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
это
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
является. Пожалуйста, смотрите раздел ниже для получения дополнительной информации по этому вопросу.это
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.
Да, как сказал Earwicker, это в значительной степени эквивалентно LINQ select
и имеет очень мало общего с Ruby и Python yield
, В принципе, где в C# вы бы написали
from ... select ???
в Scala у вас есть вместо
for ... yield ???
Также важно понимать, что for
-comprehensions работают не только с последовательностями, но и с любым типом, который определяет определенные методы, например LINQ:
- Если ваш тип определяет просто
map
, Это позволяетfor
-выражения, состоящие из одного генератора. - Если это определяет
flatMap
так же какmap
, Это позволяетfor
-выражения, состоящие из нескольких генераторов. - Если это определяет
foreach
, Это позволяетfor
- циклы без выхода (как с одним, так и с несколькими генераторами). - Если это определяет
filter
, Это позволяетfor
-фильтров выражения, начинающиеся сif
вfor
выражение.
Если вы не получите лучший ответ от пользователя Scala (которым я не являюсь), вот мое понимание.
Он появляется только как часть выражения, начинающегося с for
, в котором говорится, как создать новый список из существующего списка.
Что-то вроде:
var doubled = for (n <- original) yield n * 2
Таким образом, есть один элемент вывода для каждого ввода (хотя я считаю, что есть способ отбрасывания дубликатов).
Это сильно отличается от "императивных продолжений", доступных в yield в других языках, где он дает возможность генерировать список любой длины из некоторого императивного кода практически с любой структурой.
(Если вы знакомы с C#, это ближе к LINQ select
оператор, чем это yield return
).
Рассмотрим следующее для понимания
val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i
Может быть полезно прочитать это вслух следующим образом
" За каждое целое число i
если оно больше чем 3
, а затем дать (произвести) i
и добавить его в список A
".
С точки зрения математической нотации построителя множества вышеприведенное "понимание" аналогично
который может быть прочитан как
" За каждое целое число если оно больше чем тогда он входит в набор ".
или в качестве альтернативы
" множество всех целых так, что каждый больше, чем ".
Ключевое слово yield
в Scala просто синтаксический сахар, который может быть легко заменен map
Даниэль Собрал уже объяснил подробно.
С другой стороны, yield
абсолютно вводит в заблуждение, если вы ищете генераторы (или продолжения), подобные тем, что есть в Python. См. Эту ветку SO для получения дополнительной информации: каков предпочтительный способ реализации yield в Scala?
Выход аналогичен циклу 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, в))
Надеюсь это поможет!!
Согласно документации Scala , там четко сказано «получить новую коллекцию из существующей коллекции».
В другой документации Scala говорится: «Scala предлагает облегченную нотацию для выражения вложений последовательности . Включения имеют формуfor
(перечисления)yield e
, где enums относится к списку перечислителей, разделенных точкой с запятой. Перечислитель — это либо генератор, вводящий новые переменные, либо фильтр. "
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 )
Эти две части кода также эквивалентны.
Карта так же гибка, как доходность и наоборот.
val doubledNums = for (n <- nums) yield n * 2
val ucNames = for (name <- names) yield name.capitalize
Обратите внимание, что оба этих выражения for используют ключевое слово yield :
Использование yield после for - это «секретный соус», который гласит: «Я хочу получить новую коллекцию из существующей коллекции, которую я повторяю в выражении for, используя показанный алгоритм».
взято отсюда
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), что, вероятно, не то, что вы намереваетесь.