Contoh yang Anda berikan hanya menggunakan nilai panggilan, jadi saya akan memberikan contoh baru yang lebih sederhana yang menunjukkan perbedaan.
Pertama, mari kita asumsikan kita memiliki fungsi dengan efek samping. Fungsi ini mencetak sesuatu dan kemudian mengembalikan Int
.
def something() = {
println("calling something")
1 // return value
}
Sekarang kita akan mendefinisikan dua fungsi yang menerima Int
argumen yang persis sama kecuali bahwa satu mengambil argumen dalam gaya panggilan-menurut-nilai ( x: Int
) dan yang lainnya dalam gaya panggilan-dengan-nama ( x: => Int
).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Sekarang apa yang terjadi ketika kita memanggil mereka dengan fungsi efek samping kita?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Jadi, Anda dapat melihat bahwa dalam versi nilai panggilan, efek samping dari panggilan fungsi lewat ( something()
) hanya terjadi sekali. Namun, dalam versi panggilan-dengan-nama, efek samping terjadi dua kali.
Ini karena fungsi panggilan-oleh-nilai menghitung nilai ekspresi yang dilewatkan sebelum memanggil fungsi, sehingga nilai yang sama diakses setiap kali. Sebagai gantinya, fungsi panggilan-nama- menghitung ulang nilai ekspresi yang dilewatkan setiap kali diakses.
=> Int
adalah tipe yang berbeda dariInt
; "fungsi tanpa argumen yang akan menghasilkanInt
" vs adilInt
. Setelah Anda memiliki fungsi kelas satu, Anda tidak perlu menciptakan terminologi nama panggilan untuk menjelaskan hal ini.f(2)
dikompilasi sebagai ekspresi tipeInt
, kode yang dihasilkan memanggilf
dengan argumen2
dan hasilnya adalah nilai ekspresi. Jika teks yang sama dikompilasi sebagai ekspresi tipe=> Int
maka kode yang dihasilkan menggunakan referensi ke semacam "blok kode" sebagai nilai ekspresi. Apa pun itu, nilai dari tipe itu dapat diteruskan ke fungsi yang mengharapkan parameter dari tipe itu. Saya cukup yakin Anda bisa melakukan ini dengan penugasan variabel, tanpa melewati parameter yang terlihat. Jadi apa hubungannya nama atau panggilan dengan itu?=> Int
"fungsi tidak ada argumen yang menghasilkan Int", bagaimana bedanya() => Int
? Scala tampaknya memperlakukan ini secara berbeda, misalnya=> Int
tampaknya tidak berfungsi sebagai tipe aval
, hanya sebagai tipe parameter.=> Int
adalah kenyamanan, dan itu tidak diimplementasikan persis seperti objek fungsi (mungkin mengapa Anda tidak dapat memiliki variabel tipe=> Int
, meskipun tidak ada alasan mendasar mengapa ini tidak bisa berfungsi).() => Int
adalah eksplisit fungsi tanpa argumen yang akan mengembalikanInt
, yang perlu disebut secara eksplisit dan dapat dilalui sebagai fungsi.=> Int
adalah semacam "proxyInt
", dan satu - satunya hal yang dapat Anda lakukan dengannya adalah memanggilnya (secara implisit) untuk mendapatkanInt
.Berikut ini contoh dari Martin Odersky:
Kami ingin memeriksa strategi evaluasi dan menentukan mana yang lebih cepat (kurang langkah) dalam kondisi ini:
nilai panggilan: tes (2,3) -> 2 * 2 -> 4
panggilan menurut nama: tes (2,3) -> 2 * 2 -> 4
Di sini hasilnya dicapai dengan jumlah langkah yang sama.
nilai panggilan: tes (7,8) -> 7 * 7 -> 49
panggilan dengan nama: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Di sini panggilan menurut nilai lebih cepat.
panggilan berdasarkan nilai: tes (7,8) -> 7 * 7 -> 49
panggilan berdasarkan nama: 7 * 7 -> 49
Di sini panggilan berdasarkan nama lebih cepat
panggilan berdasarkan nilai: test (7,2 * 4) -> test (7, 8) -> 7 * 7 -> 49
panggilan dengan nama: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Hasilnya tercapai dalam langkah yang sama.
sumber
def test (x:Int, y: => Int) = x * x
catatan bahwa parameter y tidak pernah digunakan.Dalam hal contoh Anda, semua parameter akan dievaluasi sebelum dipanggil dalam fungsi, karena Anda hanya mendefinisikannya berdasarkan nilai . Jika Anda ingin mendefinisikan parameter Anda dengan nama Anda harus melewati blok kode:
Dengan cara ini parameter
x
tidak akan dievaluasi sampai dipanggil dalam fungsi.Posting kecil ini di sini juga menjelaskan hal ini dengan baik.
sumber
Untuk mengulang titik @ Ben dalam komentar di atas, saya pikir yang terbaik adalah memikirkan "panggilan-dengan-nama" sebagai gula sintaksis saja. Parser hanya membungkus ekspresi dalam fungsi anonim, sehingga mereka bisa dipanggil pada titik nanti, ketika mereka digunakan.
Akibatnya, alih-alih mendefinisikan
dan berjalan:
Anda juga bisa menulis:
Dan jalankan sebagai berikut untuk efek yang sama:
sumber
=> T
dan() => T
. Fungsi yang menggunakan tipe pertama sebagai parameter, tidak akan menerima yang kedua, scala menyimpan informasi yang cukup dalam@ScalaSignature
anotasi untuk membuang kesalahan waktu kompilasi untuk ini. Bytecode untuk keduanya=> T
dan() => T
sama dan adalah aFunction0
. Lihat pertanyaan ini untuk lebih jelasnya.Saya akan mencoba menjelaskan dengan use case yang sederhana daripada hanya dengan memberikan contoh
Bayangkan Anda ingin membangun "aplikasi kerikil" yang akan membuat Anda Nag setiap kali sejak terakhir kali Anda terganggu.
Periksa implementasi berikut:
Dalam implementasi di atas, nagger akan bekerja hanya ketika melewati nama alasannya adalah, ketika melewati nilai akan digunakan kembali dan oleh karena itu nilai tidak akan dievaluasi kembali sedangkan ketika melewati nama nilai akan dievaluasi kembali setiap waktu variabel diakses
sumber
Biasanya, parameter untuk fungsi adalah parameter menurut nilai; yaitu, nilai parameter ditentukan sebelum dilewatkan ke fungsi. Tetapi bagaimana jika kita perlu menulis fungsi yang menerima sebagai parameter ekspresi yang tidak ingin kita evaluasi sampai dipanggil dalam fungsi kita? Untuk keadaan ini, Scala menawarkan parameter panggilan-dengan-nama.
Mekanisme panggilan demi nama meneruskan blok kode ke callee dan setiap kali callee mengakses parameter, blok kode dieksekusi dan nilainya dihitung.
sumber
Seperti yang saya asumsikan,
call-by-value
fungsi seperti yang dibahas di atas hanya meneruskan nilai ke fungsi. MenurutMartin Odersky
Ini adalah strategi Evaluasi diikuti oleh Scala yang memainkan peran penting dalam evaluasi fungsi. Tapi, buat itu mudahcall-by-name
. itu seperti melewatkan fungsi sebagai argumen ke metode juga dikenal sebagaiHigher-Order-Functions
. Ketika metode mengakses nilai parameter yang diteruskan, ia memanggil implementasi fungsi yang dilewati. seperti di bawah ini:Menurut contoh @dhg, buat metode terlebih dahulu sebagai:
Fungsi ini berisi satu
println
pernyataan dan mengembalikan nilai integer. Buat fungsi, yang memiliki argumen sebagaicall-by-name
:Parameter fungsi ini, adalah mendefinisikan fungsi anonim yang mengembalikan satu nilai integer. Dalam hal ini
x
berisi definisi fungsi yang telah0
melewati argumen tetapi mengembalikanint
nilai dansomething
fungsi kami berisi tanda tangan yang sama. Saat kami memanggil fungsi, kami meneruskan fungsi sebagai argumencallByName
. Tetapi dalam kasuscall-by-value
ini hanya meneruskan nilai integer ke fungsi. Kami memanggil fungsi seperti di bawah ini:Dalam
something
metode ini kita dipanggil dua kali, karena ketika kita mengakses nilaix
dalamcallByName
metode, itu panggilan ke definisisomething
metode.sumber
Panggilan berdasarkan nilai adalah kasus penggunaan umum seperti yang dijelaskan oleh banyak jawaban di sini ..
Saya akan mencoba mendemonstrasikan panggilan dengan nama dengan cara yang lebih sederhana dengan menggunakan case di bawah ini
Contoh 1:
Contoh sederhana / kasus penggunaan panggilan dengan nama di bawah ini berfungsi, yang mengambil fungsi sebagai parameter dan memberikan waktu berlalu.
Contoh 2:
apache spark (with scala) menggunakan logging menggunakan panggilan dengan nama cara melihat
Logging
sifat di mana malas mengevaluasi apakahlog.isInfoEnabled
atau tidak dari metode di bawah ini.sumber
Dalam Panggilan dengan Nilai , nilai ekspresi dipra-komputasi pada saat pemanggilan fungsi dan nilai tertentu diteruskan sebagai parameter ke fungsi terkait. Nilai yang sama akan digunakan di seluruh fungsi.
Sedangkan dalam Call by Name , ekspresi itu sendiri dilewatkan sebagai parameter ke fungsi dan itu hanya dihitung di dalam fungsi, setiap kali parameter tertentu dipanggil.
Perbedaan antara Call by Name dan Call by Value dalam Scala dapat lebih dipahami dengan contoh di bawah ini:
Cuplikan Kode
Keluaran
Dalam cuplikan kode di atas, untuk panggilan fungsi CallbyValue (System.nanoTime ()) , waktu sistem nano sudah dihitung sebelumnya dan bahwa nilai yang sudah dihitung sebelumnya telah melewati parameter ke panggilan fungsi.
Tetapi dalam pemanggilan fungsi CallbyName (System.nanoTime ()) , ekspresi "System.nanoTime ())" itu sendiri diteruskan sebagai parameter ke pemanggilan fungsi dan nilai ekspresi itu dihitung ketika parameter itu digunakan di dalam fungsi. .
Perhatikan definisi fungsi fungsi CallbyName, di mana ada simbol => yang memisahkan parameter x dan datatype-nya. Simbol khusus di sana menunjukkan fungsi panggilan berdasarkan jenis nama.
Dengan kata lain, argumen fungsi panggilan menurut nilai dievaluasi satu kali sebelum memasukkan fungsi, tetapi argumen fungsi panggilan dengan nama dievaluasi di dalam fungsi hanya saat dibutuhkan.
Semoga ini membantu!
sumber
Berikut adalah contoh cepat yang saya kodekan untuk membantu seorang rekan saya yang saat ini mengambil kursus Scala. Apa yang saya pikir menarik adalah bahwa Martin tidak menggunakan jawaban pertanyaan && yang disajikan sebelumnya dalam kuliah sebagai contoh. Bagaimanapun saya harap ini membantu.
Output dari kode adalah sebagai berikut:
sumber
Parameter biasanya dilewati oleh nilai, yang berarti bahwa mereka akan dievaluasi sebelum diganti dalam fungsi tubuh.
Anda dapat memaksa parameter untuk dipanggil dengan nama dengan menggunakan panah ganda saat mendefinisikan fungsi.
sumber
Sudah ada banyak jawaban fantastis untuk pertanyaan ini di Internet. Saya akan menulis kompilasi dari beberapa penjelasan dan contoh yang telah saya kumpulkan tentang topik tersebut, kalau-kalau ada orang yang merasa terbantu
PENGANTAR
call-by-value (CBV)
Biasanya, parameter ke fungsi adalah parameter nilai-panggilan; yaitu, parameter dievaluasi dari kiri ke kanan untuk menentukan nilainya sebelum fungsi itu sendiri dievaluasi
call-by-name (CBN)
Tetapi bagaimana jika kita perlu menulis fungsi yang menerima sebagai parameter ekspresi yang tidak kita evaluasi sampai dipanggil dalam fungsi kita? Untuk keadaan ini, Scala menawarkan parameter panggilan-dengan-nama. Berarti parameter dilewatkan ke fungsi sebagaimana adanya, dan penilaiannya terjadi setelah penggantian
Mekanisme panggilan demi nama meneruskan blok kode ke panggilan dan setiap kali panggilan mengakses parameter, blok kode dieksekusi dan nilainya dihitung. Dalam contoh berikut, tertunda mencetak pesan yang menunjukkan bahwa metode telah dimasukkan. Selanjutnya, tertunda mencetak pesan dengan nilainya. Akhirnya, pengembalian tertunda 't':
PROS DAN KONTRA UNTUK SETIAP KASUS
CBN: + Menghentikan lebih sering * memeriksa di bawah ini penghentian * + Memiliki keuntungan bahwa argumen fungsi tidak dievaluasi jika parameter yang sesuai tidak digunakan dalam evaluasi fungsi tubuh -Ini lebih lambat, itu menciptakan lebih banyak kelas (artinya program mengambil lebih lama untuk memuat) dan ia menghabiskan lebih banyak memori.
CBV: + Ini seringkali secara eksponensial lebih efisien daripada CBN, karena ia menghindari rekomputasi berulang argumen ekspresi yang memerlukan nama panggilan. Ini mengevaluasi setiap argumen fungsi hanya sekali + Ini bermain jauh lebih baik dengan efek imperatif dan efek samping, karena Anda cenderung tahu lebih baik kapan ekspresi akan dievaluasi. -Ini dapat menyebabkan loop selama evaluasi parameternya * periksa di bawah ini penghentian *
Bagaimana jika pemutusan hubungan kerja tidak dijamin?
-Jika evaluasi CBV dari ekspresi e berakhir, maka evaluasi CBN dari e berakhir juga-Arah lainnya tidak benar
Contoh non-terminasi
Pertimbangkan ekspresi terlebih dahulu (1, loop)
CBN: first (1, loop) → 1 CBV: first (1, loop) → kurangi argumen dari ungkapan ini. Karena satu adalah loop, itu mengurangi argumen tanpa batas. Itu tidak berakhir
PERBEDAAN PERILAKU SETIAP KASUS
Mari kita tentukan tes metode yang akan dilakukan
Tes Case1 (2,3)
Karena kita mulai dengan argumen yang sudah dievaluasi, itu akan menjadi jumlah langkah yang sama untuk panggilan-menurut-nilai dan panggilan-menurut-nama
Tes Case2 (3 + 4,8)
Dalam hal ini panggilan-menurut-nilai melakukan lebih sedikit langkah
Tes Case3 (7, 2 * 4)
Kami menghindari perhitungan argumen kedua yang tidak perlu
Tes Case4 (3 + 4, 2 * 4)
Pendekatan yang berbeda
Pertama, mari kita asumsikan kita memiliki fungsi dengan efek samping. Fungsi ini mencetak sesuatu dan kemudian mengembalikan Int.
Sekarang kita akan mendefinisikan dua fungsi yang menerima argumen Int yang persis sama kecuali bahwa satu mengambil argumen dalam gaya panggilan-nilai-(x: Int) dan yang lainnya dalam gaya panggilan-nama-nama (x: => Int).
Sekarang apa yang terjadi ketika kita memanggil mereka dengan fungsi efek samping kita?
Jadi, Anda dapat melihat bahwa dalam versi nilai panggilan, efek samping dari panggilan fungsi lewat (sesuatu ()) hanya terjadi sekali. Namun, dalam versi panggilan-dengan-nama, efek samping terjadi dua kali.
Ini karena fungsi panggilan-oleh-nilai menghitung nilai ekspresi yang dilewatkan sebelum memanggil fungsi, sehingga nilai yang sama diakses setiap kali. Namun, fungsi nama panggilan menghitung ulang nilai ekspresi yang dilewatkan setiap kali diakses.
CONTOH DI MANA ITU LEBIH BAIK UNTUK MENGGUNAKAN CALL-BY-NAME
Dari: https://stackoverflow.com/a/19036068/1773841
Contoh kinerja sederhana: logging.
Mari kita bayangkan sebuah antarmuka seperti ini:
Dan kemudian digunakan seperti ini:
Jika metode info tidak melakukan apa-apa (karena, katakanlah, tingkat logging dikonfigurasi untuk lebih tinggi dari itu), maka computeTimeSpent tidak pernah dipanggil, menghemat waktu. Ini sering terjadi pada logger, di mana orang sering melihat manipulasi string yang bisa mahal dibandingkan dengan tugas yang sedang dicatat.
Contoh kebenaran: operator logika.
Anda mungkin melihat kode seperti ini:
Bayangkan Anda akan mendeklarasikan && metode seperti ini:
kemudian, setiap kali ref nol, Anda akan mendapatkan kesalahan karena isSomething akan dipanggil pada nullreference sebelum diteruskan ke &&. Karena alasan ini, deklarasi yang sebenarnya adalah:
sumber
Memberi contoh akan membantu Anda lebih memahami perbedaannya.
Mari tentukan fungsi sederhana yang mengembalikan waktu saat ini:
Sekarang kita akan mendefinisikan fungsi, berdasarkan nama , yang mencetak dua kali tertunda sedetik:
Dan satu dengan nilai :
Sekarang mari kita panggil masing-masing:
Hasilnya harus menjelaskan perbedaannya. Cuplikan tersedia di sini .
sumber
CallByName
dipanggil ketika digunakan dancallByValue
dipanggil setiap kali pernyataan itu ditemui.Sebagai contoh:-
Saya memiliki loop infinite yaitu jika Anda menjalankan fungsi ini kami tidak akan pernah mendapatkan
scala
prompt.sebuah
callByName
fungsi menggunakanloop
metode di atas sebagai argumen dan tidak pernah digunakan di dalam tubuhnya.Pada pelaksanaan
callByName
metode kami tidak menemukan masalah (kami mendapatkanscala
prompt kembali) karena kami tidak ada tempat menggunakan fungsi loop di dalamcallByName
fungsi.sebuah
callByValue
fungsi menggunakanloop
metode di atas sebagai parameter karena fungsi di dalam atau ekspresi dievaluasi sebelum menjalankan fungsi luar di sana denganloop
fungsi yang dieksekusi secara rekursif dan kami tidak pernah mendapatkanscala
prompt kembali.sumber
Lihat ini:
y: => Int adalah panggilan dengan nama. Apa yang dilewati sebagai panggilan dengan nama ditambahkan (2, 1). Ini akan dievaluasi dengan malas. Jadi output pada konsol akan "mul" diikuti oleh "add", meskipun add sepertinya disebut pertama. Panggilan dengan nama bertindak sebagai jenis yang melewati penunjuk fungsi.
Sekarang ubah dari y: => Int ke y: Int. Konsol akan menampilkan "tambah" diikuti oleh "mul"! Cara evaluasi yang biasa.
sumber
Saya tidak berpikir semua jawaban di sini melakukan pembenaran yang benar:
Dalam panggilan berdasarkan nilai, argumen dihitung hanya sekali:
Anda dapat melihat di atas bahwa semua argumen dievaluasi apakah diperlukan tidak, biasanya
call-by-value
bisa cepat tetapi tidak selalu seperti dalam kasus ini.Jika strategi evaluasi itu
call-by-name
maka dekomposisi akan menjadi:seperti yang Anda lihat di atas, kami tidak pernah perlu mengevaluasi
4 * 11
dan karenanya menyimpan sedikit perhitungan yang mungkin bermanfaat kadang-kadang.sumber