Năng suất của Scala là gì?


Câu trả lời:


205

Nó được sử dụng để hiểu theo trình tự (như hiểu và liệt kê danh sách của Python, nơi bạn cũng có thể sử dụng yield).

Nó được áp dụng kết hợp với forvà viết một phần tử mới vào chuỗi kết quả.

Ví dụ đơn giản (từ 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)
  }
}

Biểu thức tương ứng trong F # sẽ là

[ for a in args -> a.toUpperCase ]

hoặc là

from a in args select a.toUpperCase 

ở Linq.

Ruby yieldcó một hiệu ứng khác.


57
Vậy tại sao tôi lại sử dụng năng suất thay vì bản đồ? Mã bản đồ này tương đương val res = args.map (_. ToUpperCase), phải không?
Geo

4
Trong trường hợp bạn thích cú pháp tốt hơn. Ngoài ra, như alexey chỉ ra, việc hiểu cũng cung cấp cú pháp tốt để truy cập FlatMap, bộ lọc và foreach.
Nathan Shively-Sanders

22
Đúng. Nếu bạn chỉ có một bản đồ đơn giản - một trình tạo không có - tôi chắc chắn sẽ nói rằng bản đồ gọi là dễ đọc hơn. Nếu bạn có một số trình tạo tùy thuộc vào nhau và / hoặc bộ lọc, bạn có thể thích biểu thức.
Alexey Romanov

13
Xin lưu ý rằng ví dụ đã cho không tương đương với biểu thức bản đồ: nó giống nhau. Một để hiểu được dịch sang các cuộc gọi đến bản đồ, bản đồ phẳng và bộ lọc.
Daniel C. Sobral

9
Câu trả lời bắt đầu như sau: "Nó được sử dụng trong việc hiểu trình tự (như hiểu và liệt kê danh sách của Python, nơi bạn cũng có thể sử dụng năng suất)." Điều này khiến người ta lầm tưởng rằng sản lượng trong Scala tương tự như sản lượng trong Python. Đây không phải là trường hợp. Trong Python, sản lượng được sử dụng trong bối cảnh của coroutines (hoặc tiếp tục) trong khi nó không phải là trường hợp trong Scala. Để làm rõ hơn, vui lòng truy cập chủ đề này: stackoverflow.com/questions/2201882/NH
Richard Gomes

817

Tôi nghĩ rằng câu trả lời được chấp nhận là tuyệt vời, nhưng có vẻ như nhiều người đã không nắm bắt được một số điểm cơ bản.

Đầu tiên, sự forhiểu biết của Scala tương đương với doký hiệu của Haskell , và nó không có gì khác hơn là một cú pháp cú pháp cho thành phần của nhiều hoạt động đơn nguyên. Vì tuyên bố này rất có thể sẽ không giúp được ai cần giúp đỡ, chúng ta hãy thử lại LỚN :-)

Sự forhiểu biết của Scala là đường cú pháp cho thành phần của nhiều hoạt động với bản đồ, flatMapfilter. Hoặc foreach. Scala thực sự chuyển một forbiểu hiện thành các cuộc gọi đến các phương thức đó, vì vậy bất kỳ lớp nào cung cấp chúng, hoặc một tập hợp con của chúng, đều có thể được sử dụng để hiểu.

Đầu tiên, hãy nói về các bản dịch. Có những quy tắc rất đơn giản:

  1. Điều này

    for(x <- c1; y <- c2; z <-c3) {...}

    được dịch sang

    c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
  2. Điều này

    for(x <- c1; y <- c2; z <- c3) yield {...}

    được dịch sang

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
  3. Điều này

    for(x <- c; if cond) yield {...}

    được dịch trên Scala 2.7 thành

    c.filter(x => cond).map(x => {...})

    hoặc, trên Scala 2.8, vào

    c.withFilter(x => cond).map(x => {...})

    với một dự phòng trước đây nếu phương thức withFilterkhông có sẵn nhưng filterlà. Vui lòng xem phần bên dưới để biết thêm thông tin về điều này.

  4. Điều này

    for(x <- c; y = ...) yield {...}

    được dịch sang

    c.map(x => (x, ...)).map((x,y) => {...})

Khi bạn nhìn vào những forhiểu biết rất đơn giản , map/ foreachcái nhìn thay thế, thực sự, tốt hơn. Tuy nhiên, khi bạn bắt đầu soạn chúng, bạn có thể dễ dàng bị lạc trong các mức ngoặc đơn và lồng nhau. Khi điều đó xảy ra, forsự hiểu biết thường rõ ràng hơn nhiều.

Tôi sẽ chỉ ra một ví dụ đơn giản và cố tình bỏ qua mọi lời giải thích. Bạn có thể quyết định cú pháp nào dễ hiểu hơn.

l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))

hoặc là

for {
  sl <- l
  el <- sl
  if el > 0
} yield el.toString.length

withFilter

Scala 2.8 đã giới thiệu một phương thức được gọi withFilter, với sự khác biệt chính là, thay vì trả về một bộ sưu tập mới, được lọc, nó sẽ lọc theo yêu cầu. Các filterphương pháp có hành vi của nó được xác định căn cứ vào tính nghiêm minh của bộ sưu tập. Để hiểu rõ hơn về điều này, chúng ta hãy xem một số Scala 2.7 với List(nghiêm ngặt) và Stream(không nghiêm ngặt):

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

Sự khác biệt xảy ra vì filterngay lập tức được áp dụng với List, trả lại một danh sách các tỷ lệ cược - kể từ khi foundfalse. Chỉ sau đó foreachđược thực thi, nhưng, đến lúc này, thay đổi foundlà vô nghĩa, như filterđã thực hiện.

Trong trường hợp Stream, điều kiện không được áp dụng ngay lập tức. Thay vào đó, vì mỗi yếu tố được yêu cầu bởi foreach, filterkiểm tra điều kiện, cho phép foreachảnh hưởng đến nó found. Nói rõ hơn, đây là mã tương đương để hiểu:

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)

Điều này gây ra nhiều vấn đề, bởi vì mọi người dự kiến ​​sẽ ifđược xem xét theo yêu cầu, thay vì được áp dụng cho toàn bộ bộ sưu tập trước đó.

Scala 2.8 được giới thiệu withFilter, luôn luôn không nghiêm ngặt, bất kể sự nghiêm ngặt của bộ sưu tập. Ví dụ sau đây cho thấy Listvới cả hai phương thức trên 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

Điều này tạo ra kết quả mà hầu hết mọi người mong đợi, mà không thay đổi cách filtercư xử. Là một lưu ý phụ, Rangeđã được thay đổi từ không nghiêm ngặt sang nghiêm ngặt giữa Scala 2.7 và Scala 2.8.


2
Có một phương thức mới với Bộ lọc trong scala 2.8. for (x <- c; if cond) ield {...} được dịch sang c.withFilter (x => cond) .map (x => {...}) trong scala2.8.
Eastsun

2
@Eastsun Đúng vậy, mặc dù cũng có dự phòng tự động. withFilterđược cho là không nghiêm ngặt, ngay cả đối với các bộ sưu tập nghiêm ngặt, xứng đáng được giải thích. Tôi sẽ xem xét điều này ...
Daniel C. Sobral

2
@Daniel: Có một sự đối xử tuyệt vời của chính chủ đề này trong "Lập trình trong Scala", bởi Oderky, et al. (Tôi chắc chắn bạn đã biết điều đó rồi). +1 để hiển thị nó.
Ralph

2 điểm đầu tiên đúng với: 1. for(x <- c; y <- x; z <-y) {...}được dịch thành c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}được dịch thànhc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik

Đây có for(x <- c; y = ...) yield {...}thực sự được dịch sang c.map(x => (x, ...)).map((x,y) => {...})? Tôi nghĩ rằng nó được dịch sang c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})hoặc tôi đang thiếu một cái gì đó?
gái mại dâm

23

Vâng, như Earwicker đã nói, nó khá giống với LINQ selectvà có rất ít liên quan đến Ruby và Python yield. Về cơ bản, nơi C # bạn sẽ viết

from ... select ??? 

trong Scala bạn có thay thế

for ... yield ???

Điều quan trọng nữa là phải hiểu rằng - các giải forpháp không chỉ hoạt động với các chuỗi, mà với bất kỳ loại nào xác định các phương thức nhất định, giống như LINQ:

  • Nếu kiểu của bạn chỉ xác định map, nó cho phép các forbiểu hiện bao gồm một trình tạo duy nhất.
  • Nếu nó xác định flatMapcũng như map, nó cho phép các forbiểu hiện bao gồm một số máy phát điện.
  • Nếu nó xác định foreach, nó cho phép for-loop mà không mang lại năng suất (cả với máy phát đơn và nhiều máy phát).
  • Nếu nó định nghĩa filter, nó cho phép các forbiểu thức lọc bắt đầu bằng một if trong forbiểu thức.

2
@Eldritch Conundrum - Điều thú vị là cùng một thứ tự trong đó đặc tả SQL gốc phác thảo. Ở đâu đó, ngôn ngữ SQL đảo ngược trật tự, nhưng nó hoàn toàn có ý nghĩa trước tiên mô tả những gì bạn đang kéo theo sau đó là những gì bạn mong đợi để thoát khỏi nó.
Jordan Parmer

13

Trừ khi bạn nhận được câu trả lời tốt hơn từ người dùng Scala (mà tôi không biết), đây là sự hiểu biết của tôi.

Nó chỉ xuất hiện như một phần của biểu thức bắt đầu bằng for, trong đó nêu cách tạo danh sách mới từ danh sách hiện có.

Cái gì đó như:

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

Vì vậy, có một mục đầu ra cho mỗi đầu vào (mặc dù tôi tin rằng có một cách để loại bỏ trùng lặp).

Điều này khác hoàn toàn với "các mệnh lệnh bắt buộc" được kích hoạt bởi năng suất trong các ngôn ngữ khác, nơi nó cung cấp một cách để tạo một danh sách có độ dài bất kỳ, từ một số mã mệnh lệnh với hầu hết mọi cấu trúc.

(Nếu bạn quen thuộc với C #, nó gần với nhà điều hành của LINQ select hơn là với nó yield return).


1
nó phải là "var doubled = for (n <- gốc) mang lại n * 2".
Russel Yang


11

Hãy xem xét những điều sau đây để hiểu

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

Có thể hữu ích để đọc thành tiếng như sau

" Đối với mỗi số nguyên i, nếu nó lớn hơn 3, thì sản lượng (sản xuất) ivà thêm nó vào danh sách A."

Về mặt ký hiệu của trình xây dựng tập hợp toán học , cách hiểu ở trên tương tự như

ký hiệu

có thể được đọc là

" Đối với mỗi số nguyên Tôi, nếu nó lớn hơn 3, thì nó là thành viên của tập hợp Một."

hoặc cách khác là

" Mộtlà tập hợp của tất cả các số nguyên Tôi, sao cho mỗi số nguyên Tôilớn hơn 3."


2

Năng suất tương tự như vòng lặp có bộ đệm mà chúng ta không thể nhìn thấy và với mỗi mức tăng, nó tiếp tục thêm mục tiếp theo vào bộ đệm. Khi vòng lặp for kết thúc chạy, nó sẽ trả về tập hợp tất cả các giá trị mang lại. Năng suất có thể được sử dụng như các toán tử số học đơn giản hoặc thậm chí kết hợp với các mảng. Đây là hai ví dụ đơn giản để bạn hiểu rõ hơn

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)] = Danh sách ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))

Hi vọng điêu nay co ich!!


Khi trả lời một câu hỏi cũ này (hơn 9 năm trước), thật hữu ích khi chỉ ra câu trả lời của bạn khác với tất cả các câu trả lời khác đã được gửi như thế nào.
jwvh

Tôi nghĩ làm rõ nghi ngờ là quan trọng và không đưa ra câu trả lời khác nhau vì ngay cả tôi cũng là người mới bắt đầu học ngôn ngữ này. Cám ơn vì sự gợi ý.
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 )

Hai đoạn mã này là tương đương.

val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )

println( res3 ) 
println( res4 )

Hai đoạn mã này cũng tương đương.

Bản đồ linh hoạt như năng suất và ngược lại.


-3

năng suất linh hoạt hơn so với map (), xem ví dụ bên dưới

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 )

năng suất sẽ in kết quả như: Danh sách (5, 6), tốt

while map () sẽ trả về kết quả như: List (false, false, true, true, true), có lẽ không phải là điều bạn dự định.


4
Sự so sánh đó là sai. Bạn đang so sánh hai điều khác nhau. Biểu thức trong sản lượng không có cách nào làm điều tương tự như biểu thức trong bản đồ. Ngoài ra, nó không thể hiện "tính linh hoạt" của năng suất so với bản đồ.
dotnetN00b
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.