Scala'nın verimi nedir?


Yanıtlar:


205

Sekans kavrayışlarında kullanılır (Python'un liste kavrayışları ve siz de kullanabileceğiniz jeneratörler gibi yield).

Kombinasyon ile birlikte uygulanır forve ortaya çıkan sıraya yeni bir eleman yazar.

Basit örnek ( scala-lang'den )

/** 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 # 'daki karşılık gelen ifade

[ for a in args -> a.toUpperCase ]

veya

from a in args select a.toUpperCase 

Linq.

Ruby'nin yieldfarklı bir etkisi var.


57
Öyleyse neden harita yerine verimi kullanayım? Bu harita kodu eşdeğerdir val res = args.map (_. ToUpperCase), değil mi?
Geo

4
Sözdizimini daha iyi sevmeniz durumunda. Ayrıca, alexey'in işaret ettiği gibi, kavrayışlar flatMap, filtre ve foreach'a erişmek için güzel bir sözdizimi de sağlar.
Nathan Shively-Sanders

22
Sağ. Sadece basit bir haritanız varsa - eğer hayırsa bir jeneratör - kesinlikle haritayı çağırmanın daha okunabilir olduğunu söyleyebilirim. Birbirine bağlı olarak birkaç jeneratörünüz ve / veya filtreleriniz varsa, ifade için bir tercih edebilirsiniz.
Alexey Romanov

13
Lütfen verilen örneğin harita ifadesine eşdeğer olmadığını unutmayın: aynı. Anlama için A, harita, flatMap ve filtreleme çağrılarına çevrilir.
Daniel C.Sobral

9
Cevap şu şekilde başlar: "Dizi anlamalarında kullanılır (Python'un liste anlamaları ve verimi de kullanabileceğiniz jeneratörler gibi)." Bu yanlışlıkla Scala'daki verimin Python'daki verime benzer olduğunu düşünmesine yol açar. Olay bu değil. Python'da verim, Cocautinler (veya devamlar) bağlamında kullanılırken, Scala'da durum böyle değildir. Daha fazla açıklama için lütfen bu konuyu ziyaret edin: stackoverflow.com/questions/2201882/…
Richard Gomes

817

Kabul edilen cevabın harika olduğunu düşünüyorum, ancak birçok insan bazı temel noktaları kavrayamadı.

Birincisi, Scala'nın forkavrayışları Haskell'in donotasyonuna eşdeğerdir ve çoklu monadik operasyonların kompozisyonu için sözdizimsel bir şekerden başka bir şey değildir. Bu ifade büyük olasılıkla yardıma ihtiyacı olan kimseye yardım etmeyeceğinden, tekrar deneyelim… :-)

Scala'nın forkavrayışları, harita ile çoklu operasyonların kompozisyonu için sözdizimsel şekerdir flatMapve filter. Veya foreach. Scala aslında bir forifadeyi bu yöntemlere yapılan çağrılara çevirir , böylece bunları sağlayan herhangi bir sınıf ya da bir alt kümesi kavrama için kullanılabilir.

Önce çeviriler hakkında konuşalım. Çok basit kurallar var:

  1. Bu

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

    çevrildi

    c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
  2. Bu

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

    çevrildi

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
  3. Bu

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

    Scala 2.7'ye çevrildi

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

    veya Scala 2.8'de

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

    yöntem withFiltermevcut değilse ancak mevcutsa eski haline geri dönüş filter. Bununla ilgili daha fazla bilgi için lütfen aşağıdaki bölüme bakın.

  4. Bu

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

    çevrildi

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

Çok basit forkavrayışlara baktığınızda, map/ foreachalternatifleri gerçekten daha iyi görünür. Ancak bunları oluşturmaya başladığınızda, parantez ve yuvalama düzeylerinde kolayca kaybolabilirsiniz. Bu olduğunda, forkavrayışlar genellikle çok daha açıktır.

Basit bir örnek göstereceğim ve kasten herhangi bir açıklamayı atlayacağım. Hangi sözdiziminin daha kolay anlaşılacağına karar verebilirsiniz.

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

veya

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

withFilter

Scala 2.8 withFilter, ana farkı, yeni, filtrelenmiş bir koleksiyon döndürmek yerine, talep üzerine filtrelemek olduğu adlı bir yöntem sundu . filterYöntem koleksiyonun katılığından dayalı tanımlanan davranışını vardır. Bunu daha iyi anlamak için, List(katı) ve Stream(katı olmayan ) bazı Scala 2.7'ye bir göz atalım :

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

Fark, olur filterderhal uygulanır Listberi - oran listesini dönen, foundbir false. Ancak o foreachzaman yürütülür, ancak bu zamana kadar, değişiklik zaten yürütüldüğü foundgibi anlamsızdır filter.

Durumunda, durum Streamhemen uygulanmaz. Her eleman tarafından istenen Bunun yerine, foreach, filtersağlayan koşulu, test foreachbunu yoluyla etkileme found. Sadece açıklığa kavuşturmak için, eşdeğer anlama kodu şöyledir:

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)

Bu, birçok soruna neden oldu, çünkü insanlar ifönceden tüm koleksiyona uygulamak yerine talep üzerine düşünülmesini beklediler .

Koleksiyonun katılığı ne olursa olsun withFilter, her zaman katı olmayan Scala 2.8 piyasaya sürüldü . Aşağıdaki örnek ListScala 2.8'de her iki yöntemle de gösterilmiştir :

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

Bu, çoğu insanın nasıl filterdavrandığını değiştirmeden beklediği sonucu üretir . Bir yan not olarak, RangeScala 2.7 ve Scala 2.8 arasında katı olmayandan katıya değiştirildi.


2
Scala 2.8'de Filtre ile yeni bir yöntem var. (x <- c; koşul varsa) verimi {...}, scala2.8'de c.withFilter (x => koşul) .map (x => {...}) olarak çevrilir.
Eastsun

2
@Eastsun Yeterince doğru, ancak otomatik geri dönüş de var. withFilterbazı açıklamaları hak eden katı koleksiyonlar için bile katı olmamalıdır. Bunu düşüneceğim ...
Daniel C. Sobral

2
@Daniel: "Scala'da Programlama" da bu konunun büyük bir tedavisi var, Odersky ve ark. (Eminim bunu zaten biliyorsun). Göstermek için +1.
Ralph

İlk 2 puan ile doğru: 1. for(x <- c; y <- x; z <-y) {...}çevrilir c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}çevrilirc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik

Bu for(x <- c; y = ...) yield {...}gerçekten çevrilmiş c.map(x => (x, ...)).map((x,y) => {...})mi? Bence çevrilmiş c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})ya da bir şey eksik mi?
Prostynick

23

Evet, Earwicker'ın dediği gibi, LINQ'lara eşdeğerdir selectve Ruby's ve Python'larla çok az ilgisi vardır yield. Temel olarak, nerede C # yazmak istiyorsunuz

from ... select ??? 

Scala'da bunun yerine

for ... yield ???

for-Anlamaların sadece dizilerle değil, aynı zamanda LINQ gibi belirli yöntemleri tanımlayan herhangi bir türle de çalıştığını anlamak da önemlidir :

  • Türünüz sadece tanımlıysa map, fortek bir oluşturucudan oluşan-ifadelerine izin verir .
  • Eğer tanımladığı flatMapgibi map, for-bir kaç jeneratörden oluşan-ifadelerine izin verir .
  • Tanımlarsa foreach, forverimsiz döngülere izin verir (hem tekli hem de çoklu jeneratörlerle).
  • O tanımlarsa filter, bu izin veren forbir ile başlayan süzgeç ifadeleri if de forifade.

2
@Erritch Conundrum - İlginç bir şekilde, orijinal SQL spesifikasyonunun ana hatlarıyla aynı sırada olduğu. Yol boyunca bir yerde SQL dili düzeni tersine çevirdi, ancak önce ne çektiğinizi ve ardından bundan ne beklediğinizi açıklamak tam anlamıyla mantıklı.
Jordan Parmer

13

Bir Scala kullanıcısından daha iyi bir cevap alamadığınız sürece (ki ben değilim), işte benim anlayışım.

Yalnızca for, mevcut bir listeden nasıl yeni bir liste oluşturulacağını belirten bir ifadenin parçası olarak görünür .

Gibi bir şey:

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

Yani her giriş için bir çıktı öğesi var (her ne kadar kopyaları bırakmanın bir yolu olduğuna inanıyorum).

Bu, herhangi bir uzunlukta bir liste oluşturmanın neredeyse her yapıya sahip bazı zorunlu kodlardan bir yolunu sağladığı diğer dillerde verimle sağlanan "zorunlu sürekliliklerden" oldukça farklıdır.

(C # hakkında bilginiz varsa, LINQ select operatörüne olduğundan daha yakındır yield return).


1
"var iki katına çıkarılmış = (n <- orijinal) verim n * 2" olmalıdır.
Russel Yang


11

Aşağıdaki anlayış için düşünün

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

Aşağıdaki gibi yüksek sesle okumak yararlı olabilir

" İçin her bir tamsayı i, eğer bu daha büyük 3, daha sonra elde (üretim) ive listeye ekleyin A."

Matematiksel küme oluşturucu gösterimi açısından , yukarıdaki kavrama aşağıdakilere benzerdir:

set notasyonu

hangi olarak okunabilir

" İçin her bir tamsayı ben, eğer bu daha büyük 3, daha sonra bir üyesidir dizi bir."

veya alternatif olarak

" birtüm tamsayıların kümesidir ben, öyle ki her benbiri büyüktür 3."


2

Verim, göremediğimiz bir arabelleğe sahip olan döngüye benzer ve her bir artış için arabelleğe bir sonraki öğe eklemeye devam eder. For döngüsü çalışmayı bitirdiğinde, elde edilen tüm değerlerin toplanmasını döndürür. Verim basit aritmetik operatörler olarak veya hatta dizilerle kombinasyon halinde kullanılabilir. Daha iyi anlamanız için iki basit örnek

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

res: scala.collection.immutable.IndexedSeq [Int] = Vektör (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)] = Liste ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))

Bu yardımcı olur umarım!!


Bu eski (9 yıl önce) bir soruyu cevaplarken, cevabınızın daha önce gönderilen diğer cevaplardan nasıl farklı olduğunu belirtmek yararlı olacaktır.
jwvh

Şüpheyi açıklığa kavuşturmanın önemli olduğunu düşündüm ve farklı bir cevap vermemeliydim çünkü ben de bu dili öğrenen bir acemiyim. Önerin için teşekkürler.
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 )

Bu iki kod parçası eşdeğerdir.

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

println( res3 ) 
println( res4 )

Bu iki kod parçası da eşdeğerdir.

Harita verim kadar esnektir ve tersi de geçerlidir.


-3

verim haritadan () daha esnektir, aşağıdaki örneğe bakın

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 )

verim sonucu yazdırır: Liste (5, 6), ki bu iyi

map () sonucu şu şekilde döndürür: List (false, false, true, true, true), muhtemelen istediğiniz gibi değildir.


4
Bu karşılaştırma yanlış. İki farklı şeyi karşılaştırıyorsunuz. Verimdeki ifade hiçbir şekilde haritadaki ifadeyle aynı şeyi yapmaz. Ayrıca, haritaya kıyasla verimin "esnekliğini" göstermez.
dotnetN00b
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.