Apa perbedaan antara =>, () =>, dan Unit =>

153

Saya mencoba mewakili fungsi yang tidak menggunakan argumen dan tidak mengembalikan nilai (Saya mensimulasikan fungsi setTimeout dalam JavaScript, jika Anda harus tahu.)

case class Scheduled(time : Int, callback :  => Unit)

tidak dikompilasi, mengatakan "parameter` val 'mungkin bukan panggilan-dengan-nama "

case class Scheduled(time : Int, callback :  () => Unit)  

kompilasi, tetapi harus dipanggil aneh, bukan

Scheduled(40, { println("x") } )

Aku harus melakukan ini

Scheduled(40, { () => println("x") } )      

Yang juga berhasil adalah

class Scheduled(time : Int, callback :  Unit => Unit)

tetapi dipanggil dengan cara yang bahkan kurang masuk akal

 Scheduled(40, { x : Unit => println("x") } )

(Apa yang akan menjadi variabel tipe Unit?) Apa yang saya inginkan tentu saja adalah konstruktor yang dapat memanggil cara saya akan memintanya jika itu adalah fungsi biasa:

 Scheduled(40, println("x") )

Berikan bayinya botol!

Malvolio
sumber
3
Cara lain untuk menggunakan kelas kasus dengan parm dengan nama, adalah dengan meletakkannya dalam daftar parameter sekunder, misalnya case class Scheduled(time: Int)(callback: => Unit). Ini berfungsi karena daftar parameter sekunder tidak diekspos secara publik, juga tidak termasuk dalam metode equals/ yang dihasilkan hashCode.
nilskp
Beberapa aspek yang lebih menarik mengenai perbedaan antara parameter nama-nama dan fungsi 0-arity ditemukan dalam pertanyaan ini dan jawabannya. Sebenarnya apa yang saya cari ketika saya menemukan pertanyaan ini.
lex82

Jawaban:

234

Call-by-Name: => Ketik

The => Typenotasi singkatan call-by-nama, yang merupakan salah satu dari banyak cara parameter dapat dilewatkan. Jika Anda tidak terbiasa dengan mereka, saya sarankan meluangkan waktu untuk membaca artikel wikipedia itu, meskipun saat ini sebagian besar panggilan-oleh-nilai dan panggilan-oleh-referensi.

Apa artinya adalah bahwa apa yang dilewatkan diganti dengan nama nilai di dalam fungsi. Misalnya, ambil fungsi ini:

def f(x: => Int) = x * x

Jika saya menyebutnya seperti ini

var y = 0
f { y += 1; y }

Maka kode akan dieksekusi seperti ini

{ y += 1; y } * { y += 1; y }

Meskipun itu memunculkan poin tentang apa yang terjadi jika ada bentrokan nama pengidentifikasi. Dalam panggilan-dengan-nama tradisional, suatu mekanisme yang disebut subtitusi penghindaran terjadi untuk menghindari perselisihan nama. Di Scala, bagaimanapun, ini diimplementasikan dengan cara lain dengan hasil yang sama - nama pengidentifikasi di dalam parameter tidak dapat merujuk atau pengidentifikasi bayangan dalam fungsi yang dipanggil.

Ada beberapa hal lain yang terkait dengan panggilan-nama-yang akan saya bicarakan setelah menjelaskan dua lainnya.

0-arity Fungsi: () => Ketik

Sintaksnya () => Typeadalah tipe a Function0. Yaitu, fungsi yang tidak mengambil parameter dan mengembalikan sesuatu. Ini sama dengan, katakanlah, memanggil metode size()- tidak memerlukan parameter dan mengembalikan nomor.

Sangat menarik, bagaimanapun, bahwa sintaks ini sangat mirip dengan sintaks untuk fungsi literal anonim , yang merupakan penyebab kebingungan. Sebagai contoh,

() => println("I'm an anonymous function")

adalah fungsi anonim literal dari arity 0, yang tipenya adalah

() => Unit

Jadi kita bisa menulis:

val f: () => Unit = () => println("I'm an anonymous function")

Namun, penting untuk tidak membingungkan tipe dengan nilainya.

Unit => Jenis

Ini sebenarnya hanya sebuah Function1, yang parameter pertamanya bertipe Unit. Cara lain untuk menulisnya adalah (Unit) => Typeatau Function1[Unit, Type]. Masalahnya adalah ... ini tidak mungkin menjadi apa yang diinginkan seseorang. The UnitTujuan utama tipe ini mengindikasikan nilai salah satu tidak tertarik, jadi tidak masuk akal untuk menerima nilai tersebut.

Pertimbangkan, misalnya,

def f(x: Unit) = ...

Apa yang bisa dilakukan seseorang x? Ini hanya dapat memiliki nilai tunggal, jadi orang tidak perlu menerimanya. Satu kemungkinan penggunaan adalah fungsi chaining kembali Unit:

val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g

Karena andThenhanya didefinisikan Function1, dan fungsi-fungsi yang kita gunakan kembali Unit, kita harus mendefinisikannya sebagai tipe Function1[Unit, Unit]untuk dapat mengaitkannya.

Sumber Kebingungan

Sumber pertama kebingungan adalah memikirkan kesamaan antara tipe dan literal yang ada untuk fungsi 0-arity juga ada untuk panggilan-dengan-nama. Dengan kata lain, memikirkan itu, karena

() => { println("Hi!") }

adalah untuk harfiah () => Unit, maka

{ println("Hi!") }

akan menjadi literal untuk => Unit. Bukan itu. Itu adalah blok kode , bukan literal.

Sumber kebingungan lainnya adalah bahwa nilaiUnit type ditulis , yang terlihat seperti daftar parameter 0-arity (tetapi tidak).()

Daniel C. Sobral
sumber
Saya mungkin harus menjadi orang pertama yang memilih setelah dua tahun. Seseorang bertanya-tanya tentang sintaks case => pada Natal, dan saya tidak bisa merekomendasikan jawaban ini sebagai kanonik dan lengkap! Apa yang akan terjadi dengan dunia? Mungkin bangsa Maya baru saja libur seminggu. Apakah mereka menghitung tahun kabisat dengan benar? Tabungan siang hari?
som-snytt
@ som-snytt Nah, pertanyaannya tidak bertanya case ... =>, jadi saya tidak menyebutkannya. Sedih tapi benar. :-)
Daniel C. Sobral
1
@Daniel C. Sobral dapat Anda jelaskan "Itu adalah blok kode, bukan literal." bagian. Jadi apa perbedaan yang tepat antara keduanya?
nish1013
2
@ nish1013 "literal" adalah nilai (integer 1, karakter 'a', string "abc", atau fungsi () => println("here"), untuk beberapa contoh). Itu bisa dilewatkan sebagai argumen, disimpan dalam variabel, dll. "Blok kode" adalah pembatas statik sintaksis - ini bukan nilai, tidak bisa diedarkan, atau semacamnya.
Daniel C. Sobral
1
@Alex Itu perbedaan yang sama dengan (Unit) => Typevs () => Type- yang pertama adalah Function1[Unit, Type], sedangkan yang kedua adalah a Function0[Type].
Daniel C. Sobral
36
case class Scheduled(time : Int, callback :  => Unit)

The casepengubah membuat implisit valdari setiap argumen ke konstruktor. Oleh karena itu (seperti yang dicatat seseorang) jika Anda menghapus caseAnda dapat menggunakan parameter panggilan-dengan-nama. Kompiler mungkin bisa mengizinkannya, tetapi mungkin akan mengejutkan orang-orang jika itu dibuat val callbackalih-alih diubah menjadi lazy val callback.

Ketika Anda mengubah ke callback: () => Unitsekarang kasing Anda hanya mengambil fungsi alih-alih parameter panggilan-dengan-nama. Jelas fungsi tersebut dapat disimpan val callbacksehingga tidak ada masalah.

Cara termudah untuk mendapatkan apa yang Anda inginkan (di Scheduled(40, println("x") )mana parameter panggilan-dengan-nama digunakan untuk melewati lambda) mungkin untuk melewatkan casedan secara eksplisit membuat applyyang Anda tidak bisa dapatkan di tempat pertama:

class Scheduled(val time: Int, val callback: () => Unit) {
    def doit = callback()
}

object Scheduled {
    def apply(time: Int, callback: => Unit) =
        new Scheduled(time, { () => callback })
}

Digunakan:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x
Ben Jackson
sumber
3
Mengapa tidak menjadikannya case-class dan mengganti standarnya saja? Juga, kompiler tidak dapat menerjemahkan nama-ke lazy val, karena mereka memiliki semantik yang berbeda, lazy paling banyak sekali dan dengan nama memiliki at-every-reference
Viktor Klang
@ ViktorKlang Bagaimana Anda bisa mengganti metode standar berlaku kelas kasus? stackoverflow.com/questions/2660975/…
Sawyer
objek ClassName {def apply (...): ... = ...}
Viktor Klang
Empat tahun kemudian dan saya menyadari bahwa jawaban yang saya pilih hanya menjawab pertanyaan dalam judul, bukan jawaban yang saya miliki (yang dijawab oleh orang ini).
Malvolio
1

Dalam pertanyaan, Anda ingin mensimulasikan fungsi SetTimeOut dalam JavaScript. Berdasarkan jawaban sebelumnya, saya menulis kode berikut:

class Scheduled(time: Int, cb: => Unit) {
  private def runCb = cb
}

object Scheduled {
  def apply(time: Int, cb: => Unit) = {
    val instance = new Scheduled(time, cb)
    Thread.sleep(time*1000)
    instance.runCb
  }
}

Di REPL, kita bisa mendapatkan sesuatu seperti ini:

scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b

Simulasi kami tidak berperilaku sama persis dengan SetTimeOut, karena simulasi kami adalah fungsi pemblokiran, tetapi SetTimeOut adalah non-pemblokiran.

Jeff Xu
sumber
0

Saya melakukannya dengan cara ini (hanya tidak ingin melanggar berlaku):

case class Thing[A](..., lazy: () => A) {}
object Thing {
  def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}

dan menyebutnya

Thing.of(..., your_value)
Alexey Rykhalskiy
sumber