Jaka jest różnica między „def” a „val” w celu zdefiniowania funkcji


214

Jaka jest różnica pomiędzy:

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

i

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

Oba można nazwać jak even(10).


Cześć, co to Int => Booleanznaczy? Myślę, że zdefiniowana składnia todef foo(bar: Baz): Bin = expr
Ziu,

@Ziu oznacza, że ​​funkcja „parzysta” przyjmuje wartość Int jako argument i zwraca wartość logiczną jako typ wartości. Możesz więc nazwać „parzystą (3)”, która określa logiczną „fałsz”
Denys Lobur

@DenysLobur dzięki za odpowiedź! Wszelkie odniesienia do tej składni?
Ziu

@Ziu Zasadniczo dowiedziałem się o tym z kursu Coursera Oderskiego - coursera.org/learn/progfun1 . Kiedy go ukończysz, zrozumiesz, co oznacza „Type => Type”
Denys Lobur,

Odpowiedzi:


325

Metoda def evenocenia na wywołanie i za każdym razem tworzy nową funkcję (nowe wystąpienie Function1).

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

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

Dzięki defmożesz uzyskać nową funkcję dla każdego połączenia:

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

valocenia po zdefiniowaniu, def- po wywołaniu:

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

Należy pamiętać, że jest jeszcze trzecia opcja: lazy val.

Ocenia, kiedy wywoływany jest po raz pierwszy:

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

scala> even
scala.NotImplementedError: an implementation is missing

Ale zwraca ten sam wynik (w tym przypadku to samo wystąpienie FunctionN) za każdym razem:

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

Występ

val ocenia, gdy zdefiniowano.

defocenia przy każdym połączeniu, więc wydajność może być gorsza niż w valprzypadku wielu połączeń. Za jednym razem uzyskasz taką samą wydajność. I bez połączeń nie otrzymasz żadnego obciążenia def, dzięki czemu możesz go zdefiniować, nawet jeśli nie będziesz go używać w niektórych oddziałach.

Z lazy valdostaniesz leniwe ocenę: można zdefiniować go, nawet jeśli nie będzie go używać w niektórych gałęziach, a ocenia się raz lub nigdy, ale dostaniesz trochę narzut od Blokada z podwójnym zatwierdzeniem na każdym dostępu do swoich lazy val.

Jak zauważył @SargeBorsch, możesz zdefiniować metodę, a jest to najszybsza opcja:

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

Ale jeśli potrzebujesz funkcji (nie metody) do kompozycji funkcji lub funkcji wyższego rzędu (jak filter(even)), kompilator wygeneruje funkcję z twojej metody za każdym razem, gdy używasz jej jako funkcji, więc wydajność może być nieco gorsza niż z val.


Czy mógłbyś porównać je pod względem wydajności? Nie jest ważne, aby oceniać funkcję przy każdym evenwywołaniu.
Amir Karimi,

2
defmożna użyć do zdefiniowania metody, i jest to najszybsza opcja. @ A.Karimi
Display Name

2
Dla zabawy: 2.12 even eq even.
som-snytt,

Czy istnieje koncepcja funkcji wbudowanych jak w c ++? Pochodzę ze świata c ++, więc wybacz moją ignorancję.
animageofmine

2
Kompilator @animageofmine Scala może próbować wprowadzić metody. Jest do tego @inlineatrybut . Ale nie może wstawiać funkcji, ponieważ wywołanie funkcji jest wywołaniem wirtualnej applymetody obiektu funkcji. JVM może dewirtualizować i włączać takie połączenia w niektórych sytuacjach, ale nie w ogóle.
senia

24

Rozważ to:

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

Czy widzisz różnicę? W skrócie:

def : Przy każdym wywołaniu do evenwywołuje evenponownie treść metody. Ale przy pomocy even2ie val funkcja jest inicjowana tylko raz podczas deklaracji (i dlatego wypisuje się valw linii 4 i nigdy więcej) i za każdym razem jest uzyskiwana ta sama moc wyjściowa. Na przykład spróbuj to zrobić:

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

Po xzainicjowaniu wartość zwracana przez Random.nextIntjest ustawiana jako wartość końcowa x. Następnym razem xzostanie użyte ponownie, zawsze zwróci tę samą wartość.

Możesz także leniwie inicjalizować x. tzn. przy pierwszym użyciu jest inicjowany, a nie podczas deklaracji. Na przykład:

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

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
Myślę, że twoje wyjaśnienie może sugerować coś, czego nie zamierzasz. Spróbuj zadzwonić even2dwa razy, raz z 1i raz z 2. Otrzymasz różne odpowiedzi w każdym połączeniu. Tak więc, mimo że printlnnie jest wykonywane w kolejnych połączeniach, nie otrzymujesz tego samego wyniku z różnych wywołań do even2. To, dlaczego printlnnie jest wykonywane ponownie, to inne pytanie.
melston

1
to jest naprawdę bardzo interesujące. Podobnie jak w przypadku val, czyli even2, wartość val jest szacowana do sparametryzowanej wartości. więc tak z val ci ocena funkcji, jej wartości. Println nie jest częścią wartości szacowanej. Jest to część oceny, ale nie szacowana wartość. Sztuczka polega na tym, że oszacowana wartość jest faktycznie sparametryzowaną wartością, która zależy od niektórych danych wejściowych. mądra rzecz
MaatDeamon,

1
@melston dokładnie! właśnie to zrozumiałem, więc dlaczego println nie jest wykonywany ponownie, gdy zmienia się wyjście?
aur

1
@aur to, co jest zwracane przez even2, jest właściwie funkcją (wyrażenie w nawiasach na końcu definicji even2). Ta funkcja jest wywoływana z parametrem, który przekazujesz do even2 za każdym razem, gdy ją wywołujesz.
melston

5

Zobacz:

  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)

Zaskakujące, że wydrukuje 4, a nie 9! val (nawet var) jest oceniany natychmiast i przypisywany.
Teraz zmień val na def .. wydrukuje 9! Def to wywołanie funkcji. Będzie ono oceniać przy każdym wywołaniu.


1

val tzn. „sq” jest według definicji Scali ustalony. Jest oceniany w momencie deklaracji, nie można go później zmienić. W innych przykładach, w których even2 również jest val, ale zadeklarował z podpisem funkcji tj. „(Int => Boolean)”, więc nie jest to typ Int. Jest to funkcja, a jej wartość ustawia się za pomocą następującego wyrażenia

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

Zgodnie z właściwością Scala val, nie można przypisać innej funkcji do even2, ta sama reguła co sq.

O tym, dlaczego wywoływanie funkcji eval2 val nie powoduje ciągłego wypisywania „val”?

Kod źródłowy:

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

Wiemy, że w Scali ostatnie wyrażenie powyższego rodzaju wyrażenia (wewnątrz {..}) faktycznie powraca na lewą stronę. Ostatecznie ustawiasz funkcję even2 na „x => x% 2 == 0”, co odpowiada typowi zadeklarowanemu dla typu even2 val, tj. (Int => Boolean), więc kompilator jest szczęśliwy. Teraz even2 wskazuje tylko na funkcję „(x => x% 2 == 0)” (nie jakakolwiek inna instrukcja wcześniej np. Println („val”) itp. Wywołanie event2 z różnymi parametrami faktycznie wywoła ”(x => x% 2 == 0) ”, ponieważ tylko ten zapisywany jest ze zdarzeniem2.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Aby to wyjaśnić, poniżej podano inną wersję kodu.

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

Co się stanie ? tutaj widzimy „wewnątrz końcowego fn” drukowane wielokrotnie, gdy wywołujesz even2 ().

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

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

scala> 

1

Wykonanie definicji takiej jak def x = enie oceni wyrażenia e. Zamiast e jest oceniana za każdym razem, gdy wywoływane jest x.

Alternatywnie Scala oferuje definicję wartości val x = e, która dokonuje oceny prawej strony w ramach oceny definicji. Jeśli następnie zostanie użyte x, zostanie ono natychmiast zastąpione przez wstępnie obliczoną wartość e, tak że wyrażenie nie musi być ponownie oceniane.


0

również Val jest wyceną według wartości. Co oznacza, że ​​wyrażenie po prawej stronie jest oceniane podczas definicji. Gdzie Def jest według oceny nazwy. Nie będzie oceniać, dopóki nie zostanie użyty.


0

Oprócz powyższych pomocnych odpowiedzi moje ustalenia to:

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

Powyższe pokazuje, że „def” to metoda (z zerowymi parametrami argumentu), która po wywołaniu zwraca inną funkcję „Int => Int”.

Konwersja metod na funkcje jest dobrze wyjaśniona tutaj: https://tpolecat.github.io/2014/06/09/methods-functions.html


0

W 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 oznacza call-by-name, oceniane na żądanie

val oznacza call-by-value, oceniane podczas inicjalizacji


Przy tak starym pytaniu i przy tak dużej liczbie odpowiedzi już często pomocne jest wyjaśnienie, w jaki sposób twoja odpowiedź różni się lub uzupełnia informacje zawarte w istniejących odpowiedziach.
jwvh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.