Bir işlevi tanımlamak için “def” ve “val” arasındaki fark nedir


214

Arasındaki fark nedir:

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

ve

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

Her ikisi de gibi adlandırılabilir even(10).


Merhaba, ne anlama Int => Booleangeliyor? Bence def foo(bar: Baz): Bin = expr
tanım

@Ziu, 'even' işlevinin bir argüman olarak Int aldığını ve değer türü olarak bir Boolean döndürdüğü anlamına gelir. Böylece Boolean 'false' olarak değerlendirilen 'even (3)'
diyebilirsiniz

@DenysLobur Cevabınız için teşekkürler! Bu sözdizimi hakkında referans var mı?
Ziu

@Ziu Temelde Odersky'nin Coursera kursundan öğrendim - coursera.org/learn/progfun1 .
Bitirdiğinizde,

Yanıtlar:


325

Yöntem def evençağrı üzerine değerlendirir ve her seferinde yeni işlev oluşturur (yeni örneği Function1).

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

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

İle defher çağrıda yeni bir işlev elde edebilirsiniz:

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

valtanımlandığında değerlendirir, def- çağrıldığında:

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

Üçüncü bir seçenek olduğunu unutmayın: lazy val.

İlk çağrıldığında değerlendirir:

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

scala> even
scala.NotImplementedError: an implementation is missing

Ancak FunctionNher seferinde aynı sonucu (bu durumda aynı örneğini ) döndürür :

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

Verim

val tanımlandığında değerlendirir.

defher çağrıda değerlendirilir, bu nedenle performans valbirden fazla çağrıdan daha kötü olabilir . Tek bir çağrı ile aynı performansı elde edersiniz. Ve hiçbir çağrı olmadan ek yük defalamazsınız, böylece bazı şubelerde kullanmasanız bile tanımlayabilirsiniz.

Bir ile lazy valbazı branşlarda bunu kullanmaz bile bunu tanımlayabilir ve bir kez ya da hiç değerlendirir, ancak aşağıdaki konularda her erişimine çifte kontrol kilitleme biraz yük oluşur tembel bir değerlendirme alırsınız lazy val.

@SargeBorsch'un belirttiği gibi, yöntemi tanımlayabilirsiniz ve bu en hızlı seçenektir:

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

Ancak, işlev kompozisyonu veya daha yüksek düzeydeki işlevler (gibi) için bir işleve (yönteme değil) ihtiyacınız varsa, filter(even)derleyici, işlev olarak her kullanışınızda yönteminizden bir işlev oluşturur, bu nedenle performans, bundan biraz daha kötü olabilir val.


Onları performansla karşılaştırır mısınız? Her evençağrıldığında işlevi değerlendirmek önemli değil mi ?
Amir Karimi

2
defbir yöntemi tanımlamak için kullanılabilir ve bu en hızlı seçenektir. @ A.Karimi
Görünen Ad

2
Eğlenmek için: 2.12 even eq even,.
som-snytt

C ++ gibi satır içi işlevler bir kavram var mı? C ++ dünyasından geliyorum, bu yüzden cehaletimi affedin.
animageofmine

2
@animageofmine Scala derleyicisi satır içi yöntemleri denemeye çalışabilir. Orada @inlinenitelik bunun için. Ancak işlevler satır içi yapılamaz çünkü işlev çağrısı applybir işlev nesnesinin sanal yöntemine yapılan bir çağrıdır . JVM bu gibi çağrıları bazı durumlarda devredebilir ve sıralı olarak sıralayabilir, ancak genel olarak değil.
senia

24

Bunu düşün:

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

Farkı görüyor musunuz? Kısacası:

def : Her çağrı için even, evenyöntemin gövdesini tekrar çağırır . Ancak even2ie val ile , işlev bildirim sırasında yalnızca bir kez başlatılır (ve dolayısıyla valsatır 4'e yazdırır ve bir daha asla yazdırılmaz ) ve her erişimde aynı çıktı kullanılır. Örneğin bunu yapmayı deneyin:

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

Zaman xbaşlatılır, değer tarafından döndürülen Random.nextIntnihai değeri olarak ayarlanır x. Bir dahaki sefere xtekrar kullanıldığında, her zaman aynı değeri döndürür.

Ayrıca tembel olarak başlatabilirsiniz x. yani ilk kullanıldığında deklarasyon değil, başlatılır. Örneğin:

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

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
Açıklamanızın niyet etmediğin bir şeyi ima edebileceğini düşünüyorum. even2Bir kez 1ve bir kez olmak üzere iki kez aramayı deneyin 2. Her aramada farklı cevaplar alacaksınız. Bu nedenle, printlnsonraki aramalarda yürütülmezken , farklı aramalardan aynı sonucu alamazsınız even2. Neden printlntekrar yürütülmediğine gelince , bu farklı bir soru.
melston

1
bu aslında çok ilginç. Bu val yani eşit2 durumunda olduğu gibi, val parametreli bir değere göre değerlendirilir. yani evet bir val ile fonksiyonun değerlendirilmesi, değeri. Println, değerlendirilen değerin bir parçası değildir. Değerlendirmenin bir parçasıdır, ancak değerlendirilen değerin değildir. Buradaki hile, değerlendirilen değerin aslında bazı girdilere bağlı olan parametarize bir değer olmasıdır. akıllı şey
MaatDeamon

1
melston tam olarak! Ben bunu anladım, bu yüzden çıktı değiştiğinde neden println tekrar yürütülmüyor?
aur

1
@aur even2 tarafından döndürülen şey aslında bir işlevdir (even2 tanımının sonunda parantez içindeki ifade). Bu işlev aslında her başlattığınızda even2'ye ilettiğiniz parametre ile çağrılır.
melston

5

Bunu gör:

  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)

Şaşırtıcı bir şekilde, bu 9 değil 4 yazacaktır! val (çift var) derhal değerlendirilir ve atanır.
Şimdi val değerini def olarak değiştirin .. 9 yazdıracaktır! Def bir işlev çağrısıdır .. her çağrıldığında değerlendirir.


1

val yani "sq" Scala tanımı ile sabittir. Doğru beyan edildiği anda değerlendirilir, daha sonra değiştiremezsiniz. Başka örneklerde, even2 de val'dir, ancak işlev imzasıyla bildirilir yani "(Int => Boolean)", bu nedenle Int türü değildir. Bu bir işlevdir ve değeri aşağıdaki ifadeyle ayarlanır

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

Scala val özelliğine göre, sq ile aynı kuralı even2'ye başka bir işlev atayamazsınız.

Eval2 val işlevini neden tekrar tekrar "val" yazdırmıyor?

Orig kodu:

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

Biliyoruz ki, Scala'da yukarıdaki ifade türünün son ifadesi ({..} içinde) aslında sol tarafa dönüyor. Böylece even2 val türü için (Int => Boolean) için bildirdiğiniz türle eşleşen even2 - "x => x% 2 == 0" fonksiyonunu ayarlıyorsunuz, böylece derleyici mutlu oluyor. Şimdi even2 sadece "(x => x% 2 == 0)" fonksiyonunu gösteriyor (yani println ("val") öncesi başka bir ifade değil). Farklı parametrelerle event2 çağırmak aslında "(x => x% 2) == 0) "kodu, yalnızca event2 ile kaydedildiğinden.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Sadece daha fazla açıklamak için, kodun farklı versiyonu aşağıdadır.

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

Ne olacak ? Burada, even2 () öğesini çağırdığınızda tekrar tekrar "iç final fn" nin basılı olduğunu görüyoruz.

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

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

scala> 

1

Gibi bir tanımın yürütülmesi def x = eifadeyi değerlendirmeyecektir e. Bunun yerine x her çağrıldığında e değerlendirilir.

Alternatif olarak Scala, tanımın val x = edeğerlendirmesinin bir parçası olarak sağ tarafı değerlendiren bir değer tanımı sunar . Daha sonra x kullanılırsa, hemen e'nin önceden hesaplanmış değeri ile değiştirilir, böylece ifadenin tekrar değerlendirilmesi gerekmez.


0

ayrıca, Val bir değer değerlendirmesidir. Yani sağ taraftaki ifade tanım sırasında değerlendirilir. Def ad değerlendirmesine göre. Kullanılıncaya kadar değerlendirme yapmaz.


0

Yukarıdaki faydalı yanıtlara ek olarak, bulgularım:

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

Yukarıda "def", çağrıldığında başka bir "Int => Int" işlevi döndüren bir yöntemdir (sıfır bağımsız değişken parametresi ile).

Yöntemlerin işlevlere dönüştürülmesi burada iyi açıklanmıştır: https://tpolecat.github.io/2014/06/09/methods-functions.html


0

REPL'de,

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 call-by-name, talep üzerine değerlendirilir

val call-by-value, başlatma sırasında değerlendirilen anlamına gelir


Bu kadar eski bir soru ile ve zaten çok sayıda cevap gönderildiğinde, cevabınızın mevcut cevaplarda sağlanan bilgilerden nasıl farklı olduğunu veya bunlara nasıl katkıda bulunduğunu açıklamak genellikle yararlı olur.
jwvh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.