Panggilan dengan nama vs panggilan dengan nilai dalam Scala, klarifikasi diperlukan


239

Seperti yang saya pahami, di Scala, sebuah fungsi bisa disebut juga

  • menurut nilai atau
  • dengan nama

Misalnya, mengingat deklarasi berikut, apakah kita tahu bagaimana fungsinya akan dipanggil?

Pernyataan:

def  f (x:Int, y:Int) = x;

Panggilan

f (1,2)
f (23+55,5)
f (12+3, 44*11)

Apa aturannya?

Jawaban:


540

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 Intargumen 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.


296
Saya selalu berpikir terminologi ini membingungkan. Suatu fungsi dapat memiliki beberapa parameter yang bervariasi dalam status panggilan-menurut-nama vs panggilan-menurut-nilai. Jadi bukan bahwa fungsi adalah panggilan-dengan-nama atau panggilan-oleh-nilai, itu adalah bahwa setiap parameternya mungkin lewat -oleh-nama atau lewat-nilai. Lebih jauh, "panggilan-dengan-nama" tidak ada hubungannya dengan nama . => Intadalah tipe yang berbeda dari Int; "fungsi tanpa argumen yang akan menghasilkan Int" vs adil Int. Setelah Anda memiliki fungsi kelas satu, Anda tidak perlu menciptakan terminologi nama panggilan untuk menjelaskan hal ini.
Ben

2
@ Ben, itu membantu menjawab beberapa pertanyaan, terima kasih. Saya berharap ada lebih banyak artikel yang menjelaskan semantik pass-by-name ini dengan jelas.
Christopher Poile

3
@SelimOber Jika teks f(2)dikompilasi sebagai ekspresi tipe Int, kode yang dihasilkan memanggil fdengan argumen 2dan hasilnya adalah nilai ekspresi. Jika teks yang sama dikompilasi sebagai ekspresi tipe => Intmaka 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?
Ben

4
@ Ben Jadi jika => Int"fungsi tidak ada argumen yang menghasilkan Int", bagaimana bedanya () => Int? Scala tampaknya memperlakukan ini secara berbeda, misalnya => Inttampaknya tidak berfungsi sebagai tipe a val, hanya sebagai tipe parameter.
Tim Goodman

5
@TimGoodman Anda benar, ini sedikit lebih rumit daripada yang saya lakukan. => Intadalah 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). () => Intadalah eksplisit fungsi tanpa argumen yang akan mengembalikan Int, yang perlu disebut secara eksplisit dan dapat dilalui sebagai fungsi. => Intadalah semacam "proxy Int", dan satu - satunya hal yang dapat Anda lakukan dengannya adalah memanggilnya (secara implisit) untuk mendapatkan Int.
Ben

51

Berikut ini contoh dari Martin Odersky:

def test (x:Int, y: Int)= x*x

Kami ingin memeriksa strategi evaluasi dan menentukan mana yang lebih cepat (kurang langkah) dalam kondisi ini:

test (2,3)

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.

test (3+4,8)

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.

test (7,2*4)

panggilan berdasarkan nilai: tes (7,8) -> 7 * 7 -> 49
panggilan berdasarkan nama: 7 * 7 -> 49
Di sini panggilan berdasarkan nama lebih cepat

test (3+4, 2*4) 

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.


1
Dalam contoh ketiga untuk CBV, saya pikir maksud Anda tes (7,8), bukan tes (7,14)
talonx

1
Contoh diambil dari Coursera, prinsip dalam pemrograman scala. Kuliah 1.2. Panggilan dengan nama harus membaca def test (x:Int, y: => Int) = x * xcatatan bahwa parameter y tidak pernah digunakan.
dr jerry

1
Contoh yang baik! Diambil dari Coursera MOOC :)
alxsimo

Ini adalah penjelasan yang baik tentang perbedaannya, tetapi tidak membahas pertanyaan yang diajukan, yaitu yang mana dari keduanya yang
dipanggil

16

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:

def f(x: => Int, y:Int) = x

Dengan cara ini parameter xtidak akan dievaluasi sampai dipanggil dalam fungsi.

Posting kecil ini di sini juga menjelaskan hal ini dengan baik.


10

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

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

dan berjalan:

scala> callByName(something())
calling something
x1=1
calling something
x2=1

Anda juga bisa menulis:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

Dan jalankan sebagai berikut untuk efek yang sama:

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

Saya pikir Anda maksud: <! - bahasa: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))} dan kemudian: <! - bahasa: lang-js -> callAlsoByName (() => something ()) Saya tidak berpikir Anda membutuhkan kurung kurawal di sekitar sesuatu () dalam panggilan terakhir ini. Catatan: Saya mencoba hanya mengedit jawaban Anda tetapi hasil edit saya ditolak oleh pengulas yang mengatakan bahwa itu harus berupa komentar atau jawaban yang terpisah.
lambdista

Tampaknya Anda tidak dapat menggunakan penyorotan sintaks dalam komentar, jadi abaikan saja bagian "<! - language: lang-scala ->"! Saya akan mengedit komentar saya sendiri tetapi Anda diperbolehkan melakukannya hanya dalam 5 menit! :)
lambdista

1
Saya baru-baru ini mengalami ini juga. Tidak masalah untuk berpikir secara konseptual seperti ini tetapi scala membedakan antara => Tdan () => T. Fungsi yang menggunakan tipe pertama sebagai parameter, tidak akan menerima yang kedua, scala menyimpan informasi yang cukup dalam @ScalaSignatureanotasi untuk membuang kesalahan waktu kompilasi untuk ini. Bytecode untuk keduanya => Tdan () => Tsama dan adalah a Function0. Lihat pertanyaan ini untuk lebih jelasnya.
vsnyc

6

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:

object main  {

    def main(args: Array[String]) {

        def onTime(time: Long) {
            while(time != time) println("Time to Nag!")
            println("no nags for you!")
        }

        def onRealtime(time: => Long) {
            while(time != time) println("Realtime Nagging executed!")
        }

        onTime(System.nanoTime())
        onRealtime(System.nanoTime())
    }
}

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


4

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.

object Test {
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)
  t
}
}
 1. C: /> scalac Test.scala 
 2. Tes scala
 3. Dalam metode tertunda
 4. Mendapatkan waktu dalam nano detik
 5. Param: 81303808765843
 6. Mendapatkan waktu dalam nano detik

2

Seperti yang saya asumsikan, call-by-valuefungsi seperti yang dibahas di atas hanya meneruskan nilai ke fungsi. Menurut Martin OderskyIni adalah strategi Evaluasi diikuti oleh Scala yang memainkan peran penting dalam evaluasi fungsi. Tapi, buat itu mudah call-by-name. itu seperti melewatkan fungsi sebagai argumen ke metode juga dikenal sebagai Higher-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:

def something() = {
 println("calling something")
 1 // return value
}  

Fungsi ini berisi satu printlnpernyataan dan mengembalikan nilai integer. Buat fungsi, yang memiliki argumen sebagai call-by-name:

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

Parameter fungsi ini, adalah mendefinisikan fungsi anonim yang mengembalikan satu nilai integer. Dalam hal ini xberisi definisi fungsi yang telah 0melewati argumen tetapi mengembalikan intnilai dan somethingfungsi kami berisi tanda tangan yang sama. Saat kami memanggil fungsi, kami meneruskan fungsi sebagai argumen callByName. Tetapi dalam kasus call-by-valueini hanya meneruskan nilai integer ke fungsi. Kami memanggil fungsi seperti di bawah ini:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

Dalam somethingmetode ini kita dipanggil dua kali, karena ketika kita mengakses nilai xdalam callByNamemetode, itu panggilan ke definisi somethingmetode.


2

Panggilan berdasarkan nilai adalah kasus penggunaan umum seperti yang dijelaskan oleh banyak jawaban di sini ..

Call-by-name meneruskan blok kode ke pemanggil dan setiap kali pemanggil mengakses parameter, blok kode dieksekusi dan nilainya dihitung.

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.

 /**
   * Executes some code block and prints to stdout the 
time taken to execute   the block 
for interactive testing and debugging.
   */
  def time[T](f: => T): T = {
    val start = System.nanoTime()
    val ret = f
    val end = System.nanoTime()

    println(s"Time taken: ${(end - start) / 1000 / 1000} ms")

    ret
  }

Contoh 2:

apache spark (with scala) menggunakan logging menggunakan panggilan dengan nama cara melihat Loggingsifat di mana malas mengevaluasi apakah log.isInfoEnabledatau tidak dari metode di bawah ini.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

2

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

object CallbyExample extends App {

  // function definition of call by value
  def CallbyValue(x: Long): Unit = {
    println("The current system time via CBV: " + x);
    println("The current system time via CBV " + x);
  }

  // function definition of call by name
  def CallbyName(x: => Long): Unit = {
    println("The current system time via CBN: " + x);
    println("The current system time via CBN: " + x);
  }

  // function call
  CallbyValue(System.nanoTime());
  println("\n")
  CallbyName(System.nanoTime());
}

Keluaran

The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521


The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589

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!


2

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.

val start = Instant.now().toEpochMilli

val calc = (x: Boolean) => {
    Thread.sleep(3000)
    x
}


def callByValue(x: Boolean, y: Boolean): Boolean = {
    if (!x) x else y
}

def callByName(x: Boolean, y: => Boolean): Boolean = {
    if (!x) x else y
}

new Thread(() => {
    println("========================")
    println("Call by Value " + callByValue(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


new Thread(() => {
    println("========================")
    println("Call by Name " + callByName(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


Thread.sleep(5000)

Output dari kode adalah sebagai berikut:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================

1

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.

// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1

// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)

// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))

// will not terminate, since loop(2) will evaluate. 
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 

1

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*24

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 * 749
call-by-name: (3+4)*(3+4)7 * (3+4)7 * 749

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 * 749
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 * 749
call-by-name: (3+4)*(3+4)7*(3+4)7*749

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
}

1

Memberi contoh akan membantu Anda lebih memahami perbedaannya.

Mari tentukan fungsi sederhana yang mengembalikan waktu saat ini:

def getTime = System.currentTimeMillis

Sekarang kita akan mendefinisikan fungsi, berdasarkan nama , yang mencetak dua kali tertunda sedetik:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

Dan satu dengan nilai :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

Sekarang mari kita panggil masing-masing:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

Hasilnya harus menjelaskan perbedaannya. Cuplikan tersedia di sini .


0

CallByNamedipanggil ketika digunakan dan callByValuedipanggil setiap kali pernyataan itu ditemui.

Sebagai contoh:-

Saya memiliki loop infinite yaitu jika Anda menjalankan fungsi ini kami tidak akan pernah mendapatkan scalaprompt.

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

sebuah callByNamefungsi menggunakan loopmetode di atas sebagai argumen dan tidak pernah digunakan di dalam tubuhnya.

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

Pada pelaksanaan callByNamemetode kami tidak menemukan masalah (kami mendapatkan scalaprompt kembali) karena kami tidak ada tempat menggunakan fungsi loop di dalam callByNamefungsi.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

sebuah callByValuefungsi menggunakan loopmetode di atas sebagai parameter karena fungsi di dalam atau ekspresi dievaluasi sebelum menjalankan fungsi luar di sana dengan loopfungsi yang dieksekusi secara rekursif dan kami tidak pernah mendapatkan scalaprompt kembali.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))

0

Lihat ini:

    object NameVsVal extends App {

  def mul(x: Int, y: => Int) : Int = {
    println("mul")
    x * y
  }
  def add(x: Int, y: Int): Int = {
    println("add")
    x + y
  }
  println(mul(3, add(2, 1)))
}

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.


-2

Saya tidak berpikir semua jawaban di sini melakukan pembenaran yang benar:

Dalam panggilan berdasarkan nilai, argumen dihitung hanya sekali:

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

Anda dapat melihat di atas bahwa semua argumen dievaluasi apakah diperlukan tidak, biasanya call-by-valuebisa cepat tetapi tidak selalu seperti dalam kasus ini.

Jika strategi evaluasi itu call-by-namemaka dekomposisi akan menjadi:

f(12 + 3, 4 * 11)
12 + 3
15

seperti yang Anda lihat di atas, kami tidak pernah perlu mengevaluasi 4 * 11dan karenanya menyimpan sedikit perhitungan yang mungkin bermanfaat kadang-kadang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.