Apa perbedaan antara “def” dan “val” untuk mendefinisikan suatu fungsi


214

Apa perbedaan antara:

def even: Int => Boolean = _ % 2 == 0

dan

val even: Int => Boolean = _ % 2 == 0

Keduanya bisa disebut suka even(10).


Hai, apa Int => Booleanartinya? Saya pikir sintaks define adalahdef foo(bar: Baz): Bin = expr
Ziu

@Ziu yang berarti fungsi 'even' menerima Int sebagai argumen dan mengembalikan Boolean sebagai tipe nilai. Jadi, Anda dapat memanggil 'genap (3)' yang dievaluasi menjadi Boolean 'salah'
Denys Lobur

@DenysLobur terima kasih atas balasan Anda! Adakah referensi tentang sintaks ini?
Ziu

@Ziu Saya pada dasarnya menemukannya dari kursus Coursera Odersky - coursera.org/learn/progfun1 . Pada saat Anda menyelesaikannya, Anda akan mengerti apa arti 'Type => Type'
Denys Lobur

Jawaban:


325

Metode def evenmengevaluasi panggilan dan membuat fungsi baru setiap waktu (instance baru Function1).

def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false

val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

Dengan defAnda bisa mendapatkan fungsi baru di setiap panggilan:

val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - same result

def test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - new result

valmengevaluasi ketika didefinisikan, def- ketika dipanggil:

scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing

scala> def even: Int => Boolean = ???
even: Int => Boolean

scala> even
scala.NotImplementedError: an implementation is missing

Perhatikan bahwa ada pilihan ketiga: lazy val.

Ini mengevaluasi ketika dipanggil pertama kali:

scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>

scala> even
scala.NotImplementedError: an implementation is missing

Tetapi mengembalikan hasil yang sama (dalam hal ini contoh yang sama FunctionN) setiap kali:

lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

lazy val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1068569869
test()
// Int = -1068569869 - same result

Performa

val mengevaluasi ketika didefinisikan.

defmengevaluasi setiap panggilan, sehingga kinerjanya bisa lebih buruk daripada valuntuk beberapa panggilan. Anda akan mendapatkan kinerja yang sama dengan satu panggilan. Dan tanpa panggilan, Anda tidak akan mendapatkan overhead def, sehingga Anda dapat menentukannya meskipun Anda tidak akan menggunakannya di beberapa cabang.

Dengan lazy valAnda akan mendapatkan evaluasi malas: Anda dapat mendefinisikannya bahkan jika Anda tidak akan menggunakannya di beberapa cabang, dan itu mengevaluasi sekali atau tidak pernah, tetapi Anda akan mendapatkan sedikit overhead dari penguncian cek ganda pada setiap akses ke Anda lazy val.

Seperti @SargeBorsch catat, Anda dapat mendefinisikan metode, dan ini adalah opsi tercepat:

def even(i: Int): Boolean = i % 2 == 0

Tetapi jika Anda memerlukan fungsi (bukan metode) untuk komposisi fungsi atau untuk fungsi urutan yang lebih tinggi (seperti filter(even)) kompiler akan menghasilkan fungsi dari metode Anda setiap kali Anda menggunakannya sebagai fungsi, sehingga kinerja bisa sedikit lebih buruk daripada dengan val.


Bisakah Anda membandingkannya mengenai kinerja? Bukankah penting untuk mengevaluasi fungsi setiap kali evendipanggil.
Amir Karimi

2
defdapat digunakan untuk mendefinisikan metode, dan ini adalah opsi tercepat. @ A.Karimi
Nama Tampilan

2
Untuk bersenang-senang: pada 2.12 even eq even,.
som-snytt

Apakah ada konsep fungsi inline seperti di c ++? Saya berasal dari dunia c ++, jadi maafkan ketidaktahuan saya.
animageofmine

2
@animageofmine Scala compiler dapat mencoba metode inline. Ada @inlineatribut untuk ini. Tapi itu tidak bisa inline fungsi karena pemanggilan fungsi adalah panggilan ke applymetode virtual objek fungsi. JVM mungkin melakukan devirtualise dan inline panggilan semacam itu dalam beberapa situasi, tetapi tidak secara umum.
senia

24

Pertimbangkan ini:

scala> def even: (Int => Boolean) = {
             println("def"); 
             (x => x % 2 == 0)
       }
even: Int => Boolean

scala> val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>

scala> even(1)
def
res9: Boolean = false

scala> even2(1)
res10: Boolean = false

Apakah Anda melihat perbedaannya? Pendeknya:

def : Untuk setiap panggilan even, ia memanggil tubuh evenmetode lagi. Tetapi dengan valeven2 yaitu , fungsi diinisialisasi hanya sekali saat deklarasi (dan karenanya ia mencetak pada baris 4 dan tidak pernah lagi) dan output yang sama digunakan setiap kali diakses. Misalnya coba lakukan ini:val

scala> import scala.util.Random
import scala.util.Random

scala> val x = { Random.nextInt }
x: Int = -1307706866

scala> x
res0: Int = -1307706866

scala> x
res1: Int = -1307706866

Ketika xdiinisialisasi, nilai yang dikembalikan oleh Random.nextIntditetapkan sebagai nilai akhir dari x. Lain kali xdigunakan lagi, itu akan selalu mengembalikan nilai yang sama.

Anda juga dapat dengan malas menginisialisasi x. yaitu pertama kali digunakan itu diinisialisasi dan bukan saat deklarasi. Sebagai contoh:

scala> lazy val y = { Random.nextInt }
y: Int = <lazy>

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
Saya pikir penjelasan Anda mungkin menyiratkan sesuatu yang tidak Anda maksudkan. Coba panggil even2dua kali, sekali dengan 1dan sekali dengan 2. Anda akan mendapatkan jawaban berbeda di setiap panggilan. Jadi, sementara printlntidak dieksekusi di panggilan berikutnya, Anda tidak mendapatkan hasil yang sama dari panggilan yang berbeda even2. Mengapa printlntidak dieksekusi lagi, itu pertanyaan yang berbeda.
Melston

1
itu sebenarnya sangat menarik. Seperti dalam kasus val yaitu even2, val dievaluasi ke nilai parameter. jadi ya dengan val Anda evaluasi fungsi, nilainya. Println bukan bagian dari nilai yang dievaluasi. Ini adalah bagian dari evaluasi tetapi bukan nilai yang dievaluasi. Kuncinya di sini adalah bahwa nilai yang dievaluasi sebenarnya adalah nilai parametarized, yang bergantung pada beberapa input. hal yang cerdas
MaatDeamon

1
@melston tepatnya! itulah yang saya mengerti, jadi mengapa println tidak bisa dieksekusi lagi ketika output berubah?
aur

1
@aur apa yang dikembalikan oleh even2 sebenarnya adalah sebuah fungsi (ekspresi di dalam tanda kurung di akhir definisi even2). Fungsi itu sebenarnya dipanggil dengan parameter yang Anda berikan ke even2 setiap kali Anda memohonnya.
melston

5

Lihat ini:

  var x = 2 // using var as I need to change it to 3 later
  val sq = x*x // evaluates right now
  x = 3 // no effect! sq is already evaluated
  println(sq)

Anehnya, ini akan mencetak 4 dan bukan 9! val (even var) dievaluasi segera dan ditugaskan.
Sekarang ganti val ke def .. ini akan mencetak 9! Def adalah panggilan fungsi .. itu akan mengevaluasi setiap kali dipanggil.


1

val yaitu "sq" adalah dengan definisi Scala diperbaiki. Itu dievaluasi tepat pada saat deklarasi, Anda tidak dapat mengubahnya nanti. Dalam contoh lain, di mana even2 juga val, tetapi dinyatakan dengan tanda tangan fungsi yaitu "(Int => Boolean)", jadi itu bukan tipe Int. Ini adalah fungsi dan nilainya diatur dengan mengikuti ekspresi

   {
         println("val");
         (x => x % 2 == 0)
   }

Sesuai properti Scala val, Anda tidak dapat menetapkan fungsi lain ke even2, aturan yang sama dengan sq.

Tentang mengapa memanggil fungsi val eval2 tidak mencetak "val" lagi dan lagi?

Kode asal:

val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }

Kita tahu, dalam Scala pernyataan terakhir tentang jenis ekspresi di atas (di dalam {..}) sebenarnya kembali ke sisi kiri. Jadi Anda akhirnya menetapkan even2 ke fungsi "x => x% 2 == 0", yang cocok dengan tipe yang Anda nyatakan untuk tipe val even2 yaitu (Int => Boolean), jadi kompiler senang. Sekarang even2 hanya menunjuk ke fungsi "(x => x% 2 == 0)" (bukan pernyataan lain sebelumnya yaitu println ("val") dll. Memanggil event2 dengan parameter yang berbeda akan benar-benar memanggil "(x => x% 2 == 0) "kode, karena hanya kode yang disimpan dengan event2.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Hanya untuk memperjelas ini, berikut adalah versi kode yang berbeda.

scala> val even2: (Int => Boolean) = {
     |              println("val");
     |              (x => { 
     |               println("inside final fn")
     |               x % 2 == 0
     |             })
     |        }

Apa yang akan terjadi ? di sini kita melihat "inside final fn" dicetak berulang kali, ketika Anda memanggil even2 ().

scala> even2(3)
inside final fn
res9: Boolean = false

scala> even2(2)
inside final fn
res10: Boolean = true

scala> 

1

Mengeksekusi definisi seperti def x = etidak akan mengevaluasi ekspresi e. Sebagai gantinya e dievaluasi setiap kali x dipanggil.

Sebagai alternatif, Scala menawarkan definisi nilai val x = e, yang mengevaluasi sisi kanan sebagai bagian dari evaluasi definisi. Jika x kemudian digunakan kemudian, itu segera diganti dengan nilai pra-dihitung dari e, sehingga ekspresi tidak perlu dievaluasi lagi.


0

juga, Val adalah evaluasi nilai. Yang berarti ekspresi sisi kanan dievaluasi selama definisi. Di mana Def adalah dengan evaluasi nama. Itu tidak akan mengevaluasi sampai digunakan.


0

Selain balasan yang bermanfaat di atas, temuan saya adalah:

def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int

def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int

def test3(): Int = 4
--test3: test3[]() => Int

Di atas menunjukkan bahwa "def" adalah metode (dengan parameter argumen nol) yang mengembalikan fungsi lain "Int => Int" ketika dipanggil.

Konversi metode menjadi fungsi dijelaskan dengan baik di sini: https://tpolecat.github.io/2014/06/09/methods-functions.html


0

Dalam REPL,

scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean

scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8

def berarti call-by-name, dievaluasi berdasarkan permintaan

val berarti call-by-value, dievaluasi saat inisialisasi


Dengan pertanyaan yang sudah tua ini, dan dengan begitu banyak jawaban yang sudah dikirimkan, sering kali membantu menjelaskan bagaimana jawaban Anda berbeda dari, atau menambah, informasi yang disediakan dalam jawaban yang ada.
jwvh
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.