Що таке урожайність Scala?


Відповіді:


205

Він використовується в послідовних розуміннях (як-от розуміння списків 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 

в Лінку.

Рубі yieldмає інший ефект.


57
То чому б я використовував урожайність замість карти? Цей код карти еквівалентний val res = args.map (_. ToUpperCase), правда?
Гео

4
У випадку, якщо вам подобається синтаксис краще. Крім того, як вказує Алекс, розуміння також забезпечують приємний синтаксис для доступу до FlatMap, фільтра та передбачення.
Натан Шивелі-Сандерс

22
Правильно. Якщо у вас просто проста карта - один генератор, якщо немає - я, безумовно, скажу, що карта виклику є більш читаною. Якщо у вас є кілька генераторів, що залежать один від одного, та / або фільтри, ви можете віддати перевагу для вираження.
Олексій Романов

13
Зверніть увагу, що наведений приклад не є еквівалентним виразу карти: це той самий. Для розуміння перекладається на дзвінки на карту, flatMap та фільтр.
Даніель К. Собрал

9
Відповідь починається так: "Він використовується в послідовних розуміннях (як-от, розуміння списків Python і генератори, де ви також можете використовувати вихід"). Це помилково спонукає думати, що врожайність у Scala схожа на урожайність у Python. Це не так. У Python врожайність використовується в контексті корутин (або продовжень), хоча у Scala це не так. Для отримання додаткових роз'яснень, будь ласка, відвідайте цю тему: stackoverflow.com/questions/2201882/…
Річард Гомес

817

Я вважаю, що прийнята відповідь чудова, але, здається, багато людей не змогли зрозуміти деякі основні моменти.

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

Поняття Scala - forце синтаксичний цукор для складання декількох операцій з картою flatMapта filter. Або foreach. Скала фактично переводить 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це буде розглянуто на вимогу, а не заздалегідь застосовуватися до всієї колекції.

Скала 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
Існує новий метод зFilter в scala 2.8. для (x <- c; якщо cond) вихід {...} переводиться в c.withFilter (x => cond) .карта (x => {...}) в масштабі2.8.
Еастсун

2
@Eastsun Правда, хоча є й автоматичний запасний варіант. withFilterтакож повинен бути не строгим, навіть для суворих колекцій, що заслуговує певного пояснення. Я розгляну це ...
Даніель К. Собрал

2
@Daniel: Існує велика трактування цього самого предмету в "Програмуванні в Scala", Одерський та ін. (Я впевнений, ви це вже знаєте). +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...})або мені щось не вистачає?
prostynick

23

Так, як зауважив Ервікер, це майже еквівалент LINQ selectі має дуже мало спільного з Рубі та Пітоном yield. В основному, де в C # ви б писали

from ... select ??? 

у Scala у вас замість цього

for ... yield ???

Також важливо розуміти, що for-порозуміння працюють не просто з послідовностями, а з будь-яким типом, який визначає певні методи, як LINQ:

  • Якщо ваш тип визначає просто map, він дозволяє for-вирази, що складаються з одного генератора.
  • Якщо він визначає flatMapтакож map, він дозволяє for-вирази, що складаються з декількох генераторів.
  • Якщо він визначає foreach, він дозволяє for-виходити без виходу (як з одиночними, так і з декількома генераторами).
  • Якщо він визначає filter, що дозволяє for-Filter вираження , починаючи з if в forвираженні.

2
@ Eldritch Conundrum - Що цікаво, той самий порядок, в якому окреслюються оригінальні специфікації SQL. Десь на шляху мови SQL перевернув порядок, але має повний сенс спочатку описати те, що ви тягнете, а потім те, що ви очікуєте вийти з нього.
Джордан Пармер

13

Якщо ви не отримаєте кращої відповіді від користувача Scala (чого я не є), ось моє розуміння.

Він з’являється лише як частина виразу, що починається з for, який визначає, як створити новий список із наявного списку.

Щось на зразок:

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

Таким чином, є один вихідний елемент для кожного введення (хоча я вважаю, що існує спосіб скидання дублікатів).

Це сильно відрізняється від "імперативних продовжень", що забезпечуються урожаєм в інших мовах, де він забезпечує спосіб для створення списку будь-якої довжини, з якогось імперативного коду практично з будь-якою структурою.

(Якщо ви знайомі з C #, це ближче до оператора LINQ, select ніж це yield return).


1
він повинен бути "var подвоєний = для (n <- оригінальний) вихід n * 2".
Рассел Ян

12

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

З іншого боку, yieldабсолютно вводить в оману, якщо ви шукаєте генератори (або продовження), подібні до тих, що в Python . Дивіться цю тему SO для отримання додаткової інформації: Який найкращий спосіб впровадити «урожай» у Scala?


11

Розглянемо наступне для розуміння

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

Це може бути корисно прочитати вголос наступним чином

« Для кожного цілого i, якщо воно більше 3, то вихід (продукти) iі додати його в список A

Що стосується математичних позначень побудови множини , вищезгадане для розуміння є аналогом

набір-позначення

яку можна читати як

Msgstr " Для кожного цілого числа i, якщо воно більше ніж 3, то воно є членом набору А."

або альтернативно як

" А- це набір усіх цілих чисел i, такий, що кожне iбільше, ніж 3."


2

Вихід подібний до циклу, який містить буфер, який ми не можемо бачити, і для кожного збільшення він продовжує додавати наступний елемент до буфера. Коли цикл 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)] = Список ((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 )

Ці дві частини коду також рівноцінні.

Карта така ж гнучка, як урожайність і навпаки.


-3

урожайність більш гнучка, ніж карта (), див. приклад нижче

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 () поверне результат типу: Список (помилковий, хибний, правдивий, справжній, правдивий), який, ймовірно, не є тим, що ви маєте намір.


4
Це порівняння неправильне. Ви порівнюєте дві різні речі. Вираз у врожайності жодним чином не робить те саме, що вираз у карті. Крім того, це зовсім не показує "гнучкість" врожайності порівняно з картою.
dotnetN00b
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.