ผลผลิตของสกาลาคืออะไร?


คำตอบ:


205

มันถูกใช้ในการทำความเข้าใจตามลำดับ (เช่น list-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

ทับทิมyieldมีผลแตกต่างกัน


57
แล้วทำไมฉันถึงต้องใช้ผลตอบแทนแทนแผนที่? รหัสแผนที่นี้เทียบเท่า val res = args.map (_. toUpperCase) ใช่ไหม?
Geo

4
ในกรณีที่คุณชอบไวยากรณ์ที่ดีกว่า นอกจากนี้ตามที่ alexey ชี้ให้เห็นความเข้าใจยังมีไวยากรณ์ที่ดีสำหรับการเข้าถึง flatMap ตัวกรองและ foreach
นาธาน Shively-Sanders

22
ขวา. หากคุณมีแผนที่ง่าย ๆ เครื่องกำเนิดหนึ่งที่ไม่มีถ้าฉันต้องการบอกว่าการเรียกใช้แผนที่นั้นสามารถอ่านได้ง่ายขึ้น หากคุณมีเครื่องกำเนิดไฟฟ้าหลายเครื่องขึ้นอยู่กับแต่ละอื่น ๆ และ / หรือตัวกรองคุณอาจต้องการนิพจน์
Alexey Romanov

13
โปรดทราบว่าตัวอย่างที่ให้มานั้นไม่เทียบเท่ากับนิพจน์แผนที่: เหมือนกัน เพื่อความเข้าใจถูกแปลไปยังการเรียกไปยังแผนที่ flatMap และตัวกรอง
Daniel C. Sobral

9
คำตอบเริ่มต้นดังนี้: "มันถูกใช้ในความเข้าใจตามลำดับ (เช่น Python list-comprehensions and generators ซึ่งคุณอาจใช้ผลตอบแทนเช่นกัน)" สิ่งนี้นำไปสู่การเข้าใจผิดว่าผลผลิตใน Scala นั้นคล้ายคลึงกับผลผลิตใน Python กรณีนี้ไม่ได้. ใน Python ผลตอบแทนจะถูกใช้ในบริบทของ coroutines (หรือต่อเนื่อง) ในขณะที่ไม่ใช่ใน Scala สำหรับคำชี้แจงเพิ่มเติมกรุณาเยี่ยมชมหัวข้อนี้: stackoverflow.com/questions/2201882/…
Richard Gomes

817

ฉันคิดว่าคำตอบที่ยอมรับนั้นยอดเยี่ยม แต่ดูเหมือนว่าหลายคนล้มเหลวที่จะเข้าใจประเด็นพื้นฐานบางอย่าง

ประการแรกความforเข้าใจของสกาล่าเทียบเท่ากับของแฮสเคลล์doสัญกรณ์และไม่มีอะไรมากไปกว่าน้ำตาลซินแทกติกสำหรับองค์ประกอบของการปฏิบัติการแบบ monadic หลายอย่าง เนื่องจากข้อความนี้มักจะไม่ช่วยใครก็ตามที่ต้องการความช่วยเหลือลองอีกครั้ง… :-)

Scala ของforcomprehensions เป็นน้ำตาลประโยคสำหรับองค์ประกอบของการดำเนินงานหลายแผนที่, และflatMap หรือfilter foreachสกาล่าจะแปลคำว่า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
มีวิธีการใหม่พร้อมตัวกรองในสกาล่า 2.8 สำหรับ (x <- c; if cond) ให้ผลตอบแทน {... } ถูกแปลเป็น c.withFilter (x => cond) .map (x => {... }) ใน scala2.8
Eastsun

2
@Eastun จริงพอแม้ว่าจะมีทางเลือกอัตโนมัติ withFilterควรจะไม่เข้มงวดเช่นกันแม้กระทั่งคอลเลกชันที่เข้มงวดซึ่งสมควรได้รับคำอธิบายบางอย่าง ฉันจะพิจารณาเรื่องนี้ ...
แดเนียลซี. Sobral

2
@Daniel: มีการรักษาที่ยอดเยี่ยมของเรื่องนี้ใน "Programming in Scala" โดย Odersky, et al (ฉันแน่ใจว่าคุณรู้เรื่องนี้แล้ว) +1 สำหรับแสดง
Ralph

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 => {...})))
Dominik

นี่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

ใช่อย่างที่ Earwicker พูดมันเหมือนกับของ LINQ selectและมีส่วนเกี่ยวข้องกับ Ruby และ Python เพียงเล็กน้อยyieldเท่านั้น โดยทั่วไปใน C # คุณจะเขียน

from ... select ??? 

ในสกาล่าคุณมีแทน

for ... yield ???

สิ่งสำคัญคือต้องเข้าใจว่า - ความเข้าใจforไม่เพียง แต่ทำงานกับลำดับเท่านั้น แต่ด้วยประเภทใด ๆ ที่กำหนดวิธีการบางอย่างเช่น LINQ:

  • หากประเภทของคุณกำหนดเพียงแค่mapมันจะช่วยให้for-expressions ประกอบด้วยเครื่องกำเนิดไฟฟ้าเดียว
  • ถ้ามันกำหนดflatMapเช่นเดียวกับmapมันจะช่วยให้for-expressions ประกอบด้วยเครื่องกำเนิดไฟฟ้าหลาย
  • หากกำหนดforeachจะอนุญาตให้for-loops โดยไม่มีผลตอบแทน (ทั้งที่มีเครื่องกำเนิดเดี่ยวและหลายเครื่อง)
  • ถ้ามันกำหนดfilterจะช่วยให้forการแสดงออก -filter เริ่มต้นด้วยif ในforการแสดงออก

2
@Eldritch Conundrum - สิ่งที่น่าสนใจเพียงพอคือลำดับเดียวกันกับที่ข้อมูลจำเพาะ SQL ดั้งเดิมแสดง ระหว่างทางที่ภาษา SQL กลับคำสั่งซื้อ แต่มันสมเหตุสมผลอย่างยิ่งที่จะอธิบายสิ่งที่คุณกำลังดึงจากนั้นตามด้วยสิ่งที่คุณคาดหวังว่าจะได้รับจากมัน
Jordan Parmer

13

ถ้าคุณไม่ได้รับคำตอบที่ดีกว่าจากผู้ใช้ Scala (ซึ่งฉันไม่ใช่) นี่คือความเข้าใจของฉัน

มันจะปรากฏขึ้นเป็นส่วนหนึ่งของการแสดงออกเริ่มต้นด้วยforซึ่งระบุวิธีการสร้างรายการใหม่จากรายการที่มีอยู่

สิ่งที่ต้องการ:

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

ดังนั้นจึงมีรายการเอาท์พุทหนึ่งรายการสำหรับแต่ละอินพุต (แม้ว่าฉันเชื่อว่ามีวิธีการทำสำเนาที่ซ้ำกัน)

นี่ค่อนข้างแตกต่างจาก "ความต่อเนื่องที่จำเป็น" ที่เปิดใช้งานโดยผลผลิตในภาษาอื่นซึ่งให้วิธีการสร้างรายการความยาวใด ๆ จากรหัสจำเป็นบางอย่างที่มีโครงสร้างเกือบทุกชนิด

(หากคุณคุ้นเคยกับ C # จะใกล้กับผู้ให้บริการของ LINQ selectมากกว่าที่จะเป็นyield return)


1
มันควรจะเป็น "var doubled = สำหรับ (n <- ต้นฉบับ) ให้ผลตอบแทน n * 2"
Russel Yang

12

คำหลักyieldใน Scala เป็นเพียงน้ำตาลประโยคซึ่งสามารถแทนที่ได้ง่ายโดย a mapเนื่องจากDaniel Sobral ได้อธิบายรายละเอียดไว้แล้ว

บนมืออื่น ๆ ที่yieldเป็นอย่างที่ทำให้เข้าใจผิดถ้าคุณกำลังมองหาเครื่องกำเนิดไฟฟ้า (หรือต) คล้ายกับผู้ที่อยู่ในหลาม ดูเธรด SO นี้สำหรับข้อมูลเพิ่มเติม: วิธีที่เหมาะสมในการใช้ 'yield' ใน Scala คืออะไร


11

พิจารณาความเข้าใจต่อไปนี้

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

มันอาจจะเป็นประโยชน์ในการอ่านออกเสียงดังต่อไปนี้

" สำหรับแต่ละจำนวนเต็มi, ถ้ามันเป็นมากกว่า3นั้นผลผลิต (การผลิต) iและเพิ่มเข้าไปในรายการA".

ในแง่ของสัญกรณ์ set-builderคณิตศาสตร์ข้างต้นสำหรับความเข้าใจนั้นคล้ายคลึงกับ

ชุดสัญกรณ์

ซึ่งอาจจะอ่านว่า

" สำหรับแต่ละจำนวนเต็มผม, ถ้ามันเป็นมากกว่า3นั้นก็เป็นสมาชิกคนหนึ่งของชุดA."

หรืออีกวิธีหนึ่งคือ

" Aคือชุดของจำนวนเต็มทั้งหมดผมซึ่งแต่ละชุดผมมีค่ามากกว่า3"


2

Yield นั้นคล้ายกับลูปที่มีบัฟเฟอร์ซึ่งเราไม่สามารถมองเห็นและสำหรับการเพิ่มทีละแต่ละมันยังคงเพิ่มรายการถัดไปในบัฟเฟอร์ เมื่อการวนรอบสำหรับเสร็จสิ้นการทำงานก็จะส่งกลับคอลเลกชันของค่าที่ได้ทั้งหมด Yield สามารถใช้เป็นตัวดำเนินการทางคณิตศาสตร์อย่างง่ายหรือแม้แต่ใช้ร่วมกับอาร์เรย์ ต่อไปนี้เป็นสองตัวอย่างง่ายๆเพื่อความเข้าใจที่ดีขึ้นของคุณ

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, a), (3, b), (3, c))

หวังว่านี่จะช่วยได้ !!


เมื่อตอบคำถามเก่านี้ (มากกว่า 9 ปีที่แล้ว) จะเป็นประโยชน์ในการชี้ให้เห็นว่าคำตอบของคุณแตกต่างจากคำตอบอื่น ๆ ทั้งหมดที่ส่งไปแล้ว
jwvh

ฉันคิดว่าการชี้แจงข้อสงสัยเป็นสิ่งสำคัญและไม่ให้คำตอบที่แตกต่างกันตั้งแต่ฉันยังเป็นมือใหม่ที่เรียนภาษานี้ ขอบคุณสำหรับคำแนะนำ
Manasa Chada

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 () จะแสดงผลลัพธ์ดังนี้: รายการ (false, false, true, true, true) ซึ่งอาจไม่ใช่สิ่งที่คุณตั้งใจ


4
การเปรียบเทียบนั้นผิด คุณกำลังเปรียบเทียบสองสิ่งที่แตกต่างกัน การแสดงออกของผลผลิตไม่ได้ทำในสิ่งเดียวกันกับการแสดงออกในแผนที่ นอกจากนี้มันจะไม่แสดง "ความยืดหยุ่น" ของผลตอบแทนเทียบกับแผนที่เลย
dotnetN00b
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.