Apa aturan yang tepat saat Anda dapat menghilangkan tanda kurung, titik, tanda kurung, = (fungsi), dll.?

106

Apa aturan yang tepat saat Anda dapat menghilangkan (menghilangkan) tanda kurung, titik, tanda kurung, = (fungsi), dll.?

Sebagai contoh,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service adalah objek saya
  • def findAllPresentations: Option[List[Presentation]]
  • votes kembali List[Vote]
  • must dan be adalah fungsi dari spesifikasi

Mengapa saya tidak bisa pergi:

(service findAllPresentations get first votes size) must be equalTo(2)

?

Kesalahan kompilernya adalah:

"RestServicesSpecTest.this.service.findAllPresentations dari tipe Option [Daftar [com.sharca.Presentation]] tidak mengambil parameter"

Mengapa saya berpikir saya mencoba meneruskan parameter? Mengapa saya harus menggunakan titik untuk setiap pemanggilan metode?

Why must (service.findAllPresentations get first votes size)be equalTo (2) menghasilkan:

"tidak ditemukan: nilai dulu"

Namun, "must be equalTo 2" dari (service.findAllPresentations.get.first.votes.size)must be equalTo 2, yaitu, metode rangkaian berfungsi dengan baik? - parameter rantai rantai objek.

Saya telah melihat-lihat buku dan situs web Scala dan tidak dapat menemukan penjelasan yang lengkap.

Benarkah, seperti yang dijelaskan Rob H dalam pertanyaan Stack Overflow Karakter mana yang dapat saya hilangkan di Scala? , bahwa satu-satunya kasus penggunaan yang valid untuk menghilangkan '.' adalah untuk operasi gaya "operan operator operan", dan bukan untuk rangkaian metode?

Antony Stubbs
sumber

Jawaban:

87

Anda tampaknya telah menemukan jawabannya. Bagaimanapun, saya akan mencoba menjelaskannya.

Anda dapat menghilangkan titik saat menggunakan awalan, infiks dan notasi postfix - yang disebut notasi operator . Saat menggunakan notasi operator, dan hanya setelah itu, Anda dapat menghilangkan tanda kurung jika ada kurang dari dua parameter yang diteruskan ke metode.

Sekarang, notasi operator adalah notasi untuk pemanggilan metode , yang artinya tidak dapat digunakan jika objek yang dipanggil tidak ada.

Saya akan menjelaskan secara singkat notasi.

Awalan:

Hanya ~, !, +dan -dapat digunakan dalam awalan notasi. Ini adalah notasi yang Anda gunakan saat menulis !flagatau val liability = -debt.

Infiks:

Itulah notasi di mana metode muncul di antara objek dan parameternya. Semua operator aritmatika cocok di sini.

Postfix (juga suffix):

Notasi itu digunakan ketika metode mengikuti objek dan tidak menerima parameter . Misalnya, Anda dapat menulis list tail, dan itu adalah notasi postfix.

Anda dapat menyambung panggilan notasi infix tanpa masalah, selama tidak ada metode yang dipilih. Misalnya, saya suka menggunakan gaya berikut:

(list
 filter (...)
 map (...)
 mkString ", "
)

Itu sama saja dengan:

list filter (...) map (...) mkString ", "

Sekarang, mengapa saya menggunakan tanda kurung di sini, jika filter dan peta menggunakan satu parameter? Itu karena saya memberikan fungsi anonim kepada mereka. Saya tidak dapat mencampur definisi fungsi anonim dengan gaya infix karena saya memerlukan batas untuk akhir fungsi anonim saya. Juga, definisi parameter dari fungsi anonim dapat diartikan sebagai parameter terakhir untuk metode infix.

Anda dapat menggunakan infix dengan beberapa parameter:

string substring (start, end) map (_ toInt) mkString ("<", ", ", ">")

Fungsi kari sulit digunakan dengan notasi infix. Fungsi pelipatan adalah contoh nyata dari itu:

(0 /: list) ((cnt, string) => cnt + string.size)
(list foldLeft 0) ((cnt, string) => cnt + string.size)

Anda perlu menggunakan tanda kurung di luar panggilan infix. Saya tidak yakin aturan pasti yang berlaku di sini.

Sekarang, mari kita bicara tentang postfix. Postfix bisa jadi sulit digunakan, karena tidak pernah bisa digunakan dimanapun kecuali di akhir ekspresi . Misalnya, Anda tidak dapat melakukan hal berikut:

 list tail map (...)

Karena ekor tidak muncul di akhir ekspresi. Anda juga tidak dapat melakukan ini:

 list tail length

Anda dapat menggunakan notasi infiks dengan menggunakan tanda kurung untuk menandai akhir ekspresi:

 (list tail) map (...)
 (list tail) length

Perhatikan bahwa notasi postfix tidak disarankan karena mungkin tidak aman .

Saya harap ini menghilangkan semua keraguan. Jika tidak, cukup berikan komentar dan saya akan melihat apa yang dapat saya lakukan untuk memperbaikinya.

Daniel C. Sobral
sumber
ahh, jadi Anda mengatakan bahwa dalam pernyataan saya: ((((realService findAllPresentations) get) first) votes) size) harus samaTo 2 - get, first, votes dan size adalah semua operator postfix, karena mereka tidak mengambil parameter ? jadi saya bertanya-tanya apa yang harus, menjadi dan setara ...
Antony Stubbs
Saya tidak mengatakan hal semacam itu, meskipun saya cukup yakin itu pasti masalahnya. :-) "be" mungkin adalah objek pembantu, untuk membuat sintaks lebih cantik. Atau, lebih khusus lagi, untuk mengaktifkan penggunaan notasi infix dengan "must".
Daniel C. Sobral
Nah get adalah Option.get, Pertama adalah list.first, suara adalah properti kelas kasus dan ukurannya adalah list.size. Apa yang Anda pikirkan sekarang?
Antony Stubbs
Ah ya - ini semua diperkuat oleh fakta bahwa "(realService findPresentation 1) .get.id harus sama dengan 1" berfungsi - karena Service # findPresentations (id: Int) tampaknya adalah operator infix. Keren - Saya rasa saya mengerti sekarang. :)
Antony Stubbs
42

Definisi kelas:

valatau vardapat dihilangkan dari parameter kelas yang akan membuat parameter menjadi pribadi.

Menambahkan var atau val akan membuatnya menjadi publik (yaitu, pengakses metode dan mutator dihasilkan).

{} dapat dihilangkan jika kelas tidak memiliki tubuh, yaitu,

class EmptyClass

Instansiasi kelas:

Parameter umum dapat dihilangkan jika dapat disimpulkan oleh kompilator. Namun perhatikan, jika tipe Anda tidak cocok, parameter tipe selalu disimpulkan sehingga cocok. Jadi tanpa menentukan jenisnya, Anda mungkin tidak mendapatkan apa yang Anda harapkan - yaitu, diberikan

class D[T](val x:T, val y:T);

Ini akan memberi Anda kesalahan tipe (Int ditemukan, String yang diharapkan)

var zz = new D[String]("Hi1", 1) // type error

Padahal ini berfungsi dengan baik:

var z = new D("Hi1", 1)
== D{def x: Any; def y: Any}

Karena parameter type, T, disimpulkan sebagai supertipe paling umum dari keduanya - Any.


Definisi fungsi:

= bisa dihapus jika fungsi mengembalikan Unit (tidak ada).

{}untuk badan fungsi dapat dihapus jika fungsinya adalah pernyataan tunggal, tetapi hanya jika pernyataan tersebut mengembalikan nilai (Anda memerlukan =tanda), yaitu,

def returnAString = "Hi!"

tapi ini tidak berhasil:

def returnAString "Hi!" // Compile error - '=' expected but string literal found."

Tipe kembalian dari fungsi bisa dihilangkan jika bisa disimpulkan (metode rekursif harus memiliki tipe kembalian yang ditentukan).

() dapat dihapus jika fungsi tidak mengambil argumen apa pun, yaitu,

def endOfString {
  return "myDog".substring(2,1)
}

yang menurut konvensi disediakan untuk metode yang tidak memiliki efek samping - lebih lanjut nanti.

()tidak benar-benar dijatuhkan saat menentukan parameter pass by name , tetapi sebenarnya ini adalah notasi yang sangat berbeda secara semantik, yaitu,

def myOp(passByNameString: => String)

Mengatakan myOp mengambil parameter pass-by-name, yang menghasilkan String (yaitu, ini bisa menjadi blok kode yang mengembalikan string) sebagai lawan parameter fungsi,

def myOp(functionParam: () => String)

yang mengatakan myOpmengambil fungsi yang memiliki parameter nol dan mengembalikan String.

(Pikiran Anda, parameter pass-by-name dikompilasi menjadi fungsi; itu hanya membuat sintaks lebih baik.)

() dapat dijatuhkan dalam definisi parameter fungsi jika fungsi hanya mengambil satu argumen, misalnya:

def myOp2(passByNameString:(Int) => String) { .. } // - You can drop the ()
def myOp2(passByNameString:Int => String) { .. }

Tetapi jika dibutuhkan lebih dari satu argumen, Anda harus menyertakan ():

def myOp2(passByNameString:(Int, String) => String) { .. }

Pernyataan:

.dapat dijatuhkan untuk menggunakan notasi operator, yang hanya dapat digunakan untuk operator infix (operator metode yang mengambil argumen). Lihat jawaban Daniel untuk informasi lebih lanjut.

  • . dapat juga dibuang untuk ekor daftar fungsi postfix

  • () dapat dijatuhkan untuk operator postfix list.tail

  • () tidak dapat digunakan dengan metode yang didefinisikan sebagai:

    def aMethod = "hi!" // Missing () on method definition
    aMethod // Works
    aMethod() // Compile error when calling method

Karena notasi ini dicadangkan oleh konvensi untuk metode yang tidak memiliki efek samping, seperti List # tail (yaitu, pemanggilan fungsi tanpa efek samping berarti bahwa fungsi tersebut tidak memiliki efek yang dapat diamati, kecuali untuk nilai kembaliannya).

  • () bisa dihapus untuk notasi operator saat meneruskan argumen tunggal

  • () mungkin diperlukan untuk menggunakan operator postfix yang tidak ada di akhir pernyataan

  • () mungkin diperlukan untuk menetapkan pernyataan bertingkat, ujung fungsi anonim atau untuk operator yang mengambil lebih dari satu parameter

Saat memanggil fungsi yang mengambil suatu fungsi, Anda tidak bisa menghilangkan () dari definisi fungsi bagian dalam, misalnya:

def myOp3(paramFunc0:() => String) {
    println(paramFunc0)
}
myOp3(() => "myop3") // Works
myOp3(=> "myop3") // Doesn't work

Saat memanggil fungsi yang menggunakan parameter dengan nama, Anda tidak bisa menentukan argumen sebagai fungsi anonim tanpa parameter. Misalnya, diberikan:

def myOp2(passByNameString:Int => String) {
  println(passByNameString)
}

Anda harus menyebutnya sebagai:

myOp("myop3")

atau

myOp({
  val source = sourceProvider.source
  val p = myObject.findNameFromSource(source)
  p
})

tapi tidak:

myOp(() => "myop3") // Doesn't work

IMO, terlalu sering menggunakan jenis pengembalian yang hilang dapat berbahaya bagi kode untuk digunakan kembali. Lihat saja spesifikasi untuk contoh yang baik tentang keterbacaan yang berkurang karena kurangnya informasi eksplisit dalam kode. Jumlah tingkat tipuan untuk benar-benar mencari tahu apa jenis variabel itu bisa jadi gila. Semoga alat yang lebih baik dapat mencegah masalah ini dan menjaga kode kita tetap ringkas.

(Oke, dalam pencarian untuk mengumpulkan jawaban yang lebih lengkap dan ringkas (jika saya melewatkan sesuatu, atau mendapatkan sesuatu yang salah / tidak akurat tolong beri komentar), saya telah menambahkan di awal jawaban. Harap dicatat ini bukan bahasa spesifikasi, jadi saya tidak mencoba membuatnya tepat secara akademis - hanya lebih seperti kartu referensi.)

Antony Stubbs
sumber
10
Saya menangis. Apa ini.
Profpatsch
12

Kumpulan kutipan yang memberikan wawasan tentang berbagai kondisi ...

Secara pribadi, saya pikir akan ada lebih banyak spesifikasi. Saya yakin pasti ada, saya hanya tidak mencari kata-kata yang tepat ...

Namun ada beberapa sumber, dan saya telah mengumpulkannya bersama-sama, tetapi tidak ada yang benar-benar lengkap / komprehensif / dapat dimengerti / yang menjelaskan masalah di atas kepada saya ...:

"Jika badan metode memiliki lebih dari satu ekspresi, Anda harus mengelilinginya dengan tanda kurung kurawal {…}. Anda dapat menghilangkan tanda kurung jika badan metode hanya memiliki satu ekspresi."

Dari bab 2, "Type Less, Do More", dari Programming Scala :

"Isi metode atas muncul setelah tanda sama dengan '='. Mengapa tanda sama dengan? Mengapa tidak hanya tanda kurung kurawal {…}, seperti di Java? Karena titik koma, jenis kembalian fungsi, daftar argumen metode, dan bahkan tanda kurung kurawal kadang-kadang dihilangkan, menggunakan tanda sama dengan mencegah beberapa kemungkinan ambiguitas parsing. Menggunakan tanda sama dengan juga mengingatkan kita bahwa fungsi genap adalah nilai dalam Scala, yang konsisten dengan dukungan Scala untuk pemrograman fungsional, dijelaskan lebih detail di Bab 8, Pemrograman Fungsional di Scala. "

Dari bab 1, "Zero to Sixty: Introducing Scala", dari Programming Scala :

"Sebuah fungsi tanpa parameter dapat dideklarasikan tanpa tanda kurung, dalam hal ini ia harus dipanggil tanpa tanda kurung. Ini memberikan dukungan untuk Uniform Access Principle, sehingga pemanggil tidak mengetahui apakah simbol itu variabel atau fungsi tanpa parameter.

Badan fungsi didahului oleh "=" jika mengembalikan nilai (yaitu tipe yang dikembalikan adalah sesuatu selain Unit), tetapi tipe yang dikembalikan dan "=" dapat dihilangkan ketika tipenya adalah Unit (yaitu terlihat seperti prosedur sebagai lawan dari suatu fungsi).

Kawat gigi di sekitar tubuh tidak diperlukan (jika tubuh adalah ekspresi tunggal); lebih tepatnya, isi suatu fungsi hanyalah sebuah ekspresi, dan ekspresi apa pun dengan beberapa bagian harus diapit oleh tanda kurung (ekspresi dengan satu bagian secara opsional dapat diapit oleh tanda kurung). "

"Fungsi dengan nol atau satu argumen dapat dipanggil tanpa titik dan tanda kurung. Tetapi ekspresi apa pun dapat memiliki tanda kurung di sekitarnya, sehingga Anda dapat menghilangkan titik dan tetap menggunakan tanda kurung.

Dan karena Anda bisa menggunakan tanda kurung di mana pun Anda bisa menggunakan tanda kurung, Anda bisa menghilangkan titik dan memasukkan tanda kurung, yang bisa berisi banyak pernyataan.

Fungsi tanpa argumen bisa dipanggil tanpa tanda kurung. Misalnya, fungsi length () pada String dapat dipanggil sebagai "abc" .length daripada "abc" .length (). Jika fungsinya adalah fungsi Scala yang didefinisikan tanpa tanda kurung, maka fungsi tersebut harus dipanggil tanpa tanda kurung.

Sesuai kesepakatan, fungsi tanpa argumen yang memiliki efek samping, seperti println, dipanggil dengan tanda kurung; yang tanpa efek samping disebut tanpa tanda kurung. "

Dari entri blog Scala Syntax Primer :

"Definisi prosedur adalah definisi fungsi di mana jenis hasil dan tanda sama dengan dihilangkan; ekspresi yang menentukan harus berupa blok. Misalnya, def f (ps) {stats} sama dengan def f (ps): Unit = {stats }.

Contoh 4.6.3 Berikut adalah deklarasi dan definisi dari prosedur bernama write:

trait Writer {
    def write(str: String)
}
object Terminal extends Writer {
    def write(str: String) { System.out.println(str) }
}

Kode di atas secara implisit dilengkapi dengan kode berikut:

trait Writer {
    def write(str: String): Unit
}
object Terminal extends Writer {
    def write(str: String): Unit = { System.out.println(str) }
}"

Dari spesifikasi bahasa:

"Dengan metode yang hanya mengambil satu parameter, Scala memungkinkan pengembang untuk mengganti. Dengan spasi dan menghilangkan tanda kurung, memungkinkan sintaks operator yang ditampilkan dalam contoh operator penyisipan kami. Sintaks ini digunakan di tempat lain di API Scala, seperti sebagai membangun contoh Range:

val firstTen:Range = 0 to 9

Di sini sekali lagi, to (Int) adalah metode vanilla yang dideklarasikan di dalam kelas (sebenarnya ada beberapa jenis konversi implisit di sini, tetapi Anda mendapatkan penyimpangannya). "

From Scala for Java Refugees Part 6: Getting Over Java :

"Sekarang, ketika Anda mencoba" m 0 ", Scala membuangnya sebagai operator unary, dengan alasan tidak valid (~,!, - dan +). Ia menemukan bahwa" m "adalah objek yang valid - itu adalah fungsi, bukan metode, dan semua fungsi adalah objek.

Karena "0" bukan pengenal Scala yang valid, ia tidak bisa menjadi operator infix atau postfix. Oleh karena itu, Scala mengeluh bahwa itu diharapkan ";" - yang akan memisahkan dua ekspresi (hampir) valid: "m" dan "0". Jika Anda memasukkannya, maka ia akan mengeluh bahwa m membutuhkan argumen, atau, jika gagal, "_" untuk mengubahnya menjadi fungsi yang diterapkan sebagian. "

"Saya yakin gaya sintaks operator hanya berfungsi jika Anda memiliki objek eksplisit di sisi kiri. Sintaks ini dimaksudkan agar Anda dapat mengekspresikan gaya operasi" operand operator operand "dengan cara yang alami."

Karakter mana yang dapat saya hilangkan di Scala?

Tapi yang juga membingungkan saya adalah kutipan ini:

"Perlu ada objek untuk menerima panggilan metode. Misalnya, Anda tidak dapat melakukan" println "Hello World!" "Karena println memerlukan penerima objek. Anda dapat melakukan "Console println" Hello World! "" Yang memenuhi kebutuhan. "

Karena sejauh yang saya bisa melihat, ada adalah sebuah objek untuk menerima panggilan ...

Antony Stubbs
sumber
1
Ok, jadi coba baca sumber Spesifikasi untuk mendapatkan petunjuk dan woah. Itu adalah contoh bagus dari masalah dengan kode ajaib - terlalu banyak campuran, jenis inferensi dan konversi implisit dan parameter implisit. Sangat sulit untuk memahami dari luar ke dalam! Untuk perpustakaan besar seperti itu, perkakas yang lebih baik dapat melakukan keajaiban ... suatu hari ...
Antony Stubbs
3

Saya merasa lebih mudah untuk mengikuti aturan praktis ini: dalam ruang ekspresi bergantian antara metode dan parameter. Dalam contoh Anda, (service.findAllPresentations.get.first.votes.size) must be equalTo(2)parsing sebagai (service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2)). Perhatikan bahwa tanda kurung di sekitar 2 memiliki asosiasi yang lebih tinggi daripada spasi. Titik juga memiliki asosiatif yang lebih tinggi, sehingga (service.findAllPresentations.get.first.votes.size) must be.equalTo(2)akan diuraikan sebagai (service.findAllPresentations.get.first.votes.size).must(be.equalTo(2)).

service findAllPresentations get first votes size must be equalTo 2mengurai sebagai service.findAllPresentations(get).first(votes).size(must).be(equalTo).2.

Mario Camou
sumber
2

Sebenarnya, pada bacaan kedua, mungkin ini kuncinya:

Dengan metode yang hanya mengambil satu parameter, Scala memungkinkan pengembang untuk mengganti. dengan spasi dan hilangkan tanda kurung

Seperti yang disebutkan di postingan blog: http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6 .

Jadi mungkin ini sebenarnya adalah "gula sintaks" yang sangat ketat yang hanya berfungsi saat Anda secara efektif memanggil metode, pada objek, yang menggunakan satu parameter . misalnya

1 + 2
1.+(2)

Dan tidak ada lagi.

Ini akan menjelaskan contoh saya dalam pertanyaan tersebut.

Tapi seperti yang saya katakan, jika seseorang bisa menunjukkan dengan tepat di mana dalam spesifikasi bahasa ini ditentukan, akan sangat dihargai.

Ok, beberapa orang baik (paulp_ dari #scala) telah menunjukkan di mana dalam spesifikasi bahasa informasi ini:

6.12.3: Prioritas dan asosiasi operator menentukan pengelompokan bagian ekspresi sebagai berikut.

  • Jika ada beberapa operasi infix dalam sebuah ekspresi, maka operator dengan prioritas lebih tinggi mengikat lebih dekat daripada operator dengan prioritas lebih rendah.
  • Jika ada operasi infix berurutan e0 op1 e1 op2. . .opn en dengan operator op1,. . . , jika memiliki prioritas yang sama, maka semua operator tersebut harus memiliki asosiasi yang sama. Jika semua operator adalah kiri-asosiatif, urutan diinterpretasikan sebagai (.. (E0 op1 e1) op2...) Opn en. Sebaliknya, jika semua operator adalah rightassociative, urutannya diinterpretasikan sebagai e0 op1 (e1 op2 (.. .Opn en)...).
  • Operator postfix selalu memiliki prioritas lebih rendah daripada operator infix. Misalnya e1 op1 e2 op2 selalu setara dengan (e1 op1 e2) op2.

Operand kanan dari operator asosiatif kiri dapat terdiri dari beberapa argumen yang diapit tanda kurung, misalnya e op (e1,.., En). Ekspresi ini kemudian diartikan sebagai e.op (e1,.., En).

Operasi biner asosiatif kiri e1 op e2 diinterpretasikan sebagai e1.op (e2). Jika op rightassociative, operasi yang sama diinterpretasikan sebagai {val x = e1; e2.op (x)}, di mana x adalah nama baru.

Hmm - bagi saya itu tidak sesuai dengan apa yang saya lihat atau saya hanya tidak memahaminya;)

Antony Stubbs
sumber
hmm, untuk menambah kebingungan, ini juga valid: ((((realService findAllPresentations) get) first) votes) size) harus sama dengan 2, tetapi tidak jika saya menghapus salah satu pasangan tanda kurung itu ...
Antony Stubbs
2

Tidak ada. Anda kemungkinan akan menerima saran seputar apakah fungsi tersebut memiliki efek samping atau tidak. Ini palsu. Koreksi adalah untuk tidak menggunakan efek samping sejauh diizinkan oleh Scala. Sejauh tidak bisa, maka semua taruhan dibatalkan. Semua taruhan. Menggunakan tanda kurung adalah elemen dari himpunan "semua" dan tidak berguna. Itu tidak memberikan nilai apa pun setelah semua taruhan dibatalkan.

Saran ini pada dasarnya merupakan upaya pada sistem efek yang gagal (jangan disamakan dengan: kurang berguna dibandingkan sistem efek lainnya).

Cobalah untuk tidak memberikan efek samping. Setelah itu, terimalah bahwa semua taruhan dibatalkan. Bersembunyi di balik notasi sintaksis de facto untuk sistem efek dapat dan tidak, hanya menyebabkan kerusakan.

pengguna92983
sumber
Nah, tapi itulah masalah saat Anda bekerja dengan bahasa hybrid OO / Fungsional, bukan? Dalam contoh praktis, Anda pasti ingin memiliki fungsi efek samping ... Bisakah Anda menunjukkan beberapa informasi tentang "sistem efek"? Saya pikir semakin banyak kutipan poin adalah "Sebuah fungsi tanpa parameter dapat dideklarasikan tanpa tanda kurung, dalam hal ini harus dipanggil tanpa tanda kurung Ini memberikan dukungan untuk Uniform Access Principle, sehingga pemanggil tidak tahu apakah simbol adalah variabel atau fungsi tanpa parameter. ".
Antony Stubbs