Scala'da parantez ve parantezler arasındaki resmi fark nedir ve ne zaman kullanılmalıdır?


329

Parantez ()ve parantez içindeki işlevlere argüman iletme arasındaki resmi fark nedir {}?

Scala'da Programlama kitabından aldığım duygu , Scala'nın oldukça esnek olması ve en sevdiğim şeyi kullanmam gerektiğidir, ancak bazı vakaların derlenirken bazılarının derlendiğini görüyorum.

Örneğin (sadece örnek olarak kastedildi; Genel durumu tartışan herhangi bir yanıtı takdir ediyorum, sadece bu örneği değil):

val tupleList = List[(String, String)]()
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 )

=> hata: basit ifadenin yasadışı başlatılması

val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

=> iyi.

Yanıtlar:


365

Bir kez bu konuda yazmaya çalıştım, ama kurallardan biraz dağınık olduğu için sonunda vazgeçtim. Temel olarak, asmak zorunda kalacaksınız.

Belki de kıvırcık parantezlerin ve parantezin birbirinin yerine kullanılabileceği yerlere odaklanmak en iyisidir: parametreleri yöntem çağrılarına geçirirken. Sen olabilir küme parantezleri eğer birlikte parantez değiştirin ve ancak, yöntem tek bir parametreyi bekliyor. Örneğin:

List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter

List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter

Ancak, bu kuralları daha iyi kavramak için bilmeniz gereken daha çok şey var.

Parenlerle artan derleme denetimi

Spray yazarları, yuvarlak dergi önermektedir çünkü daha fazla derleme kontrolü sağlarlar. Bu özellikle Sprey gibi DSL'ler için önemlidir. Parens kullanarak derleyiciye yalnızca tek bir satır verilmesi gerektiğini söylüyorsunuz; bu nedenle yanlışlıkla iki veya daha fazla verirseniz şikayet edecektir. Kıvırcık parantezler için durum böyle değil - örneğin bir yerde bir operatörü unutursanız, kodunuz derlenir ve beklenmedik sonuçlar ve potansiyel olarak bulmak çok zor bir hata alırsınız. Aşağıda (ifadeler saf olduğu ve en azından bir uyarı vereceği için) tartışılmıştır, ancak şu noktayı ifade eder:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

Birincisi derler, ikincisi verir error: ')' expected but integer literal found. Yazar yazmak istedi1 + 2 + 3 .

Varsayılan parametrelere sahip çok parametreli yöntemler için benzer olduğu iddia edilebilir; parenleri kullanırken parametreleri ayırmak için yanlışlıkla bir virgül unutmak imkansızdır.

lâf salatası

Ayrıntılarla ilgili önemli bir sıklıkla gözden kaçan not. Kıvırcık parantezlerin kullanılması kaçınılmaz olarak ayrıntılı koda yol açar, çünkü Scala stil kılavuzu , kıvırcık parantezlerin kapatılmasının kendi çizgisinde olması gerektiğini açıkça belirtir:

… Kapanış ayracı işlevin son satırından hemen sonra kendi satırındadır.

IntelliJ'de olduğu gibi birçok otomatik yeniden biçimlendirici bu yeniden biçimlendirmeyi sizin için otomatik olarak gerçekleştirecektir. Bu yüzden yapabildiğiniz zaman yuvarlak parens kullanmaya devam edin.

Infix Gösterimi

Infix gösterimini kullanırken, List(1,2,3) indexOf (2)tek bir parametre varsa parantezi atlayabilir ve olarak yazabilirsiniz List(1, 2, 3) indexOf 2. Nokta gösterimi söz konusu değildir.

Ayrıca, çok jetonlu bir ifade olan tek bir parametreniz olduğunda, x + 2veya gibia => a % 2 == 0 sınırlarını belirtmek için parantez kullanmanız gerektiğini unutmayın.

tuples

Parantezi bazen atlayabildiğiniz için, bazen bir tuple in gibi ekstra parantezlere ihtiyaç duyar ((1, 2))ve bazen de dış parantez atlanabilir (1, 2). Bu karışıklığa neden olabilir.

İşlev / Kısmi İşlev değişmezleri case

Scala işlev ve kısmi işlev değişmezleri için bir sözdizimine sahiptir. Şöyle görünüyor:

{
    case pattern if guard => statements
    case pattern => statements
}

caseİfadeleri kullanabileceğiniz diğer yerler matchve catchanahtar kelimeleriyle:

object match {
    case pattern if guard => statements
    case pattern => statements
}
try {
    block
} catch {
    case pattern if guard => statements
    case pattern => statements
} finally {
    block
}

caseİfadeleri başka bir bağlamda kullanamazsınız . Yani, kullanmak istiyorsanız case, ihtiyacınız var kıvırcık parantezlere . Bir işlev ile kısmi işlev arasındaki farkı tam olarak neyin ayırdığını merak ediyorsanız, cevap şudur: bağlam. Scala bir işlev beklerse, elde ettiğiniz bir işlev. Kısmi bir işlev bekleniyorsa, kısmi bir işlev alırsınız. Her ikisi de bekleniyorsa, belirsizlik hakkında bir hata verir.

İfadeler ve Bloklar

Parantez alt ifadeleri yapmak için kullanılabilir. Kıvırcık ayraçları (bu kod bloklarını yapmak için kullanılabilir değil bu yüzden bir gibi kullanmaya çalışınca dikkat, bir fonksiyon değişmezi). Bir kod bloğu, her biri bir içe aktarma ifadesi, bir bildirim veya bir ifade olabilen birden çok ifadeden oluşur. Bu böyle devam ediyor:

{
    import stuff._
    statement ; // ; optional at the end of the line
    statement ; statement // not optional here
    var x = 0 // declaration
    while (x < 10) { x += 1 } // stuff
    (x % 5) + 1 // expression
}

( expression )

Dolayısıyla, bildirimlere, birden çok ifadeye importveya buna benzer bir şeye ihtiyacınız varsa, kıvırcık parantezlere ihtiyacınız vardır. Ve bir ifade bir ifade olduğu için kıvırcık parantez içinde parantez görünebilir. Ama ilginç olan kod blokları olmasıdır de her yerde kullanabilirsiniz, böylece ifadeleri içine bir ifadesi:

( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1

Dolayısıyla, ifadeler ifadeler ve kod blokları ifadeler olduğundan, aşağıdaki her şey geçerlidir:

1       // literal
(1)     // expression
{1}     // block of code
({1})   // expression with a block of code
{(1)}   // block of code with an expression
({(1)}) // you get the drift...

Değiştirilemedikleri yerlerde

Temel olarak, yerini alamaz {}ile ()veya yardımcısı başka bir yerde tersi. Örneğin:

while (x < 10) { x += 1 }

Bu bir yöntem çağrısı değildir, bu yüzden başka bir şekilde yazamazsınız. Eh, sen, kıvırcık çiftler koyabilirsiniz için parantez conditionkullanımı parantez yanı sıra kod bloğu için küme parantezleri:

while ({x < 10}) { (x += 1) }

Umarım bu yardımcı olur.


53
İnsanlar Scala'nın karmaşık olduğunu savunuyorlar. Kendime Scala meraklısı diyebilirim.
andyczerwonka

Sanırım her yöntem için bir kapsam sunmak zorunda kalmamak Scala kodunu kolaylaştırıyor! İdeal olarak hiçbir yöntem kullanılmamalıdır {}- her şey tek bir saf ifade olmalıdır
samthebest

1
@andyczerwonka Tamamen katılıyorum ama esneklik ve ifade gücü için ödediğiniz doğal ve kaçınılmaz fiyat (?) => Scala overpriced değil. Bunun belirli bir durum için doğru seçim olup olmadığı elbette başka bir konudur.
Aşkan Kh. Nazary

Merhaba, List{1, 2, 3}.reduceLeft(_ + _)geçersiz dediğin zaman, sözdizimi hatası var mı demek istiyorsun? Ama kod derleyebilirim buluyorum. Kodumu buraya
calvin

Bunun List(1, 2, 3)yerine tüm örneklerde kullandınız List{1, 2, 3}. Ne yazık ki, Scala'nın mevcut sürümünde (2.13), bu farklı bir hata mesajı (beklenmeyen virgül) ile başarısız oluyor. Muhtemelen orijinal hatayı almak için 2.7 veya 2.8'e geri dönmelisiniz.
Daniel C. Sobral

56

Burada devam eden birkaç farklı kural ve çıkarım var: her şeyden önce, Scala bir parametre bir fonksiyon olduğunda parantezlere girer, örneğin list.map(_ * 2)parantezlerde çıkarılır, sadece daha kısa bir biçimidir list.map({_ * 2}). İkincisi, Scala son parametre listesindeki parantezleri atlamanıza izin verir, eğer bu parametre listesinin bir parametresi varsa ve bu bir işlevse, list.foldLeft(0)(_ + _)olarak yazılabilir list.foldLeft(0) { _ + _ }(ya list.foldLeft(0)({_ + _})da daha açık olmak istiyorsanız).

Ancak eklersen casediğerleri de söylediğim gibi, elde, kısmi fonksiyon yerine bir fonksiyonu, ve Scala olmaz kısmi fonksiyonlar için ayraçlar, bu yüzden anlaması list.map(case x => x * 2)olmaz işin, ancak her iki list.map({case x => 2 * 2})ve list.map { case x => x * 2 }irade.


4
Sadece son parametre listesinden değil. Örneğin list.foldLeft{0}{_+_}çalışır.
Daniel C. Sobral

1
Ah, sadece son parametre listesi olduğunu okuduğumdan emindim, ama açıkça yanılmışım! Bunu bildiğim iyi oldu.
Theo

23

Topluluktan diş teli ve parantez kullanımını standartlaştırma çabası vardır, bkz. Scala Stil Kılavuzu (sayfa 21): http://www.codecommit.com/scala-style-guide.pdf

Daha yüksek sıralı yöntem çağrıları için önerilen sözdizimi her zaman parantez kullanmak ve noktayı atlamaktır:

val filtered = tupleList takeWhile { case (s1, s2) => s1 == s2 }

"Normal" metod çağrıları için nokta ve parantezleri kullanmalısınız.

val result = myInstance.foo(5, "Hello")

18
Aslında kongre yuvarlak parantez kullanmaktır, bu bağlantı resmi değildir. Bunun nedeni işlevsel programlamada tüm işlevlerin sadece birinci derece vatandaş olmasıdır ve bu nedenle farklı muamele EDİLMEMELİDİR. İkincisi Martin Odersky, infix'i yalnızca operatör gibi yöntemler (örn +. --) İçin kullanmaya çalışmanız gerektiğini söylüyor takeWhile. Infix gösteriminin tüm noktası DSL'lere ve özel operatörlere izin vermektir, bu nedenle her zaman bu bağlamda kullanılmamalıdır.
29the14

17

Scala'da kıvırcık parantez hakkında özel veya karmaşık bir şey olduğunu düşünmüyorum. Scala'da bunların karmaşık görünmesine hakim olmak için, birkaç basit şeyi aklınızda bulundurun:

  1. Kıvrımlı parantezler, kodun son satırına göre değerlendirilen bir kod bloğu oluşturur (neredeyse tüm diller bunu yapar)
  2. kod bloğu ile istenirse bir fonksiyon oluşturulabilir (kural 1'i takip eder)
  3. Vaka yan tümcesi dışında tek satırlık kod için kıvırcık ayraçlar atlanabilir (Scala seçimi)
  4. parametre olarak kod bloğu ile fonksiyon çağrısında parantezler atlanabilir (Scala seçimi)

Yukarıdaki üç kurala göre birkaç örneği açıklayalım:

val tupleList = List[(String, String)]()
// doesn't compile, violates case clause requirement
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 ) 
// block of code as a partial function and parentheses omission,
// i.e. tupleList.takeWhile({ case (s1, s2) => s1 == s2 })
val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

// curly braces omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft(_+_)
// parentheses omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft{_+_}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).reduceLeft _+_ // res1: String => String = <function1>

// curly braces omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0)(_ + _)
// parentheses omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0){_ + _}
// block of code and parentheses omission
List(1, 2, 3).foldLeft {0} {_ + _}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).foldLeft(0) _ + _
// error: ';' expected but integer literal found.
List(1, 2, 3).foldLeft 0 (_ + _)

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }
// block of code that just evaluates to a value of a function, and parentheses omission
// i.e. foo({ println("Hey"); x => println(x) })
foo { println("Hey"); x => println(x) }

// parentheses omission, i.e. f({x})
def f(x: Int): Int = f {x}
// error: missing arguments for method f
def f(x: Int): Int = f x

1. aslında tüm dillerde doğru değildir. 4. Scala'da aslında doğru değil. Ör: def f (x: Int) = fx
aij

@aij, yorum için teşekkürler. Birincisi, Scala'nın {}davranış için sağladığı aşinalığı öneriyordum . Hassasiyet ifadelerini güncelledim. Ve 4 kişilik, biraz arasındaki etkileşim zor kaynaklanan bit var ()ve {}gibi def f(x: Int): Int = f {x}eserleri ve ben 5th vardı neden en söyledi. :)
lcn

1
İçeriği farklı şekilde ayrıştırması dışında, () ve {} 'ı Scala'da çoğunlukla değiştirilebilir olarak görme eğilimindeyim. Normalde f ({x}) yazmıyorum, bu yüzden f {x}, parantezleri kıvrımlarla değiştirmek kadar atlamak istemiyor. Diğer diller aslında parantezleri atlamanıza izin verir, Örneğin, SML'de fun f(x) = f xgeçerlidir.
aij

@aij tedavi f {x}olarak f({x})daha iyi olarak görünüyor açıklama düşünmek gibi, benim için ()ve {}daha az sezgisel değiştirilebilir. Bu arada, f({x})yorum biraz Scala spec tarafından desteklenmektedir (bölüm 6.6):ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
lcn

13

İşlev çağrılarındaki kullanımlarını ve çeşitli şeylerin neden gerçekleştiğini açıklamaya değer olduğunu düşünüyorum. Birinin daha önce söylediği gibi, kıvırcık parantezler bir kod bloğunu tanımlar, bu da bir ifadedir, böylece ifadenin beklendiği yere konabilir ve değerlendirilecektir. Değerlendirildiğinde, ifadeleri yürütülür ve sonun ifade değeri, tüm blok değerlendirmesinin sonucudur (biraz Ruby'de olduğu gibi).

Buna sahip olmak gibi şeyler yapabiliriz:

2 + { 3 }             // res: Int = 5
val x = { 4 }         // res: x: Int = 4
List({1},{2},{3})     // res: List[Int] = List(1,2,3)

Son örnek, her biri önce değerlendirilen üç parametreli bir işlev çağrısıdır.

Şimdi işlev çağrılarıyla nasıl çalıştığını görmek için, başka bir işlevi parametre olarak alan basit işlevi tanımlayalım.

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }

Bunu çağırmak için, Int türünden bir parametre alan işlevi geçmemiz gerekir, böylece işlev değişmezini kullanabilir ve foo'ya iletebiliriz:

foo( x => println(x) )

Daha önce de belirtildiği gibi, bir ifade yerine kod bloğunu kullanabiliriz, bu yüzden kullanalım

foo({ x => println(x) })

Burada olan, {} içindeki kodun değerlendirilmesidir ve fonksiyon değeri blok değerlendirmesinin bir değeri olarak döndürülür, daha sonra bu değer foo'ya geçirilir. Bu semantik olarak önceki çağrı ile aynıdır.

Ama daha fazlasını ekleyebiliriz:

foo({ println("Hey"); x => println(x) })

Şimdi kod bloğumuz iki ifade içeriyor ve foo yürütülmeden önce değerlendirildiği için, önce "Hey" yazdırılıyor, sonra işlevimiz foo'ya geçiyor, "foo giriliyor" yazdırılıyor ve son olarak "4" yazdırılıyor .

Bu biraz çirkin görünüyor ve Scala bu durumda parantezi atlamamıza izin veriyor, böylece yazabiliriz:

foo { println("Hey"); x => println(x) }

veya

foo { x => println(x) }

Bu çok daha hoş görünüyor ve öncekilere eşdeğer. Burada hala kod bloğu değerlendirilir ve değerlendirme sonucu (x => println (x)) foo'ya argüman olarak iletilir.


1
Sadece ben miyim. ama aslında açık özelliğini tercih ediyorum foo({ x => println(x) }). Belki de kendi
yolumda

7

Kullandığınız için case, kısmi bir fonksiyon tanımlıyorsunuz ve kısmi fonksiyonlar kıvırcık parantez gerektiriyor.


1
Genel olarak bir cevap istedim, sadece bu örnek için bir cevap değil.
Marc-François

5

Parenlerle artan derleme denetimi

Sprey yazarları, yuvarlak parenslerin daha fazla derleme kontrolü vermelerini önerir. Bu özellikle Sprey gibi DSL'ler için önemlidir. Parens kullanarak derleyiciye sadece tek bir satır verilmesi gerektiğini söylüyorsunuz, bu nedenle yanlışlıkla iki veya daha fazla verdiyseniz şikayet edecektir. Kıvırcık parantezlerde durum böyle değil, örneğin, kodunuzun derleneceği bir yerde bir operatörü unutursanız, beklenmedik sonuçlar ve potansiyel olarak bulmak çok zor bir hata alırsınız. Aşağıda (ifadeler saf olduğu ve en azından bir uyarı vereceği için) kontrendikedir, ancak

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

İlk derlemeler, ikincisi error: ')' expected but integer literal found.yazarın yazmak istediğini verir 1 + 2 + 3.

Varsayılan parametrelere sahip çok parametreli yöntemler için benzer olduğu iddia edilebilir; parenleri kullanırken parametreleri ayırmak için yanlışlıkla bir virgül unutmak imkansızdır.

lâf salatası

Ayrıntılarla ilgili önemli bir sıklıkla gözden kaçan not. Kıvrık parantez kullanmak kaçınılmaz olarak ayrıntılı koda yol açar çünkü scala stil kılavuzu, kıvırcık parantezlerin kapatılmasının kendi satırında olması gerektiğini açıkça belirtir: http://docs.scala-lang.org/style/declarations.html "... kapanış ayracı , işlevin son satırından hemen sonra kendi satırındadır. " Intellij'de olduğu gibi birçok otomatik yeniden biçimlendirici bu yeniden biçimlendirmeyi sizin için otomatik olarak gerçekleştirecektir. Bu yüzden yapabildiğiniz zaman yuvarlak parens kullanmaya devam edin. Örneğin List(1, 2, 3).reduceLeft{_ + _}:

List(1, 2, 3).reduceLeft {
  _ + _
}

-2

Parantez ile, sizin için uyarılmış noktalı virgül var ve parantez yok. takeWhileİşlevi düşünün , kısmi işlev beklediğinden, büyük {case xxx => ??? }/ küçük harf ifadesi etrafındaki parantez yerine yalnızca geçerli tanımdır.

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.