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
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
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
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
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':
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
Dalam metode tertunda
Mendapatkan waktu dalam nano detik
Param: 2027245119786400
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
def first(x:Int, y:Int)=x
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
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Tes Case1 (2,3)
test(2,3) → 2*2 → 4
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)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
Dalam hal ini panggilan-menurut-nilai melakukan lebih sedikit langkah
Tes Case3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Kami menghindari perhitungan argumen kedua yang tidak perlu
Tes Case4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
Pendekatan yang berbeda
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 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).
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 (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:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
Dan kemudian digunakan seperti ini:
logger.info("Time spent on X: " + computeTimeSpent)
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:
if (ref != null && ref.isSomething)
Bayangkan Anda akan mendeklarasikan && metode seperti ini:
trait Boolean {
def &&(other: Boolean): Boolean
}
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:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> 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.