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)
.
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)
.
Odpowiedzi:
Metoda def even
ocenia 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 def
moż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
val
ocenia 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.
def
ocenia przy każdym połączeniu, więc wydajność może być gorsza niż w val
przypadku 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 val
dostaniesz 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
.
even
wywołaniu.
def
można użyć do zdefiniowania metody, i jest to najszybsza opcja. @ A.Karimi
even eq even
.
@inline
atrybut . Ale nie może wstawiać funkcji, ponieważ wywołanie funkcji jest wywołaniem wirtualnej apply
metody obiektu funkcji. JVM może dewirtualizować i włączać takie połączenia w niektórych sytuacjach, ale nie w ogóle.
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 even
wywołuje even
ponownie treść metody. Ale przy pomocy even2
ie val funkcja jest inicjowana tylko raz podczas deklaracji (i dlatego wypisuje się val
w 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 x
zainicjowaniu wartość zwracana przez Random.nextInt
jest ustawiana jako wartość końcowa x
. Następnym razem x
zostanie 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
even2
dwa razy, raz z 1
i raz z 2
. Otrzymasz różne odpowiedzi w każdym połączeniu. Tak więc, mimo że println
nie jest wykonywane w kolejnych połączeniach, nie otrzymujesz tego samego wyniku z różnych wywołań do even2
. To, dlaczego println
nie jest wykonywane ponownie, to inne pytanie.
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.
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>
Wykonanie definicji takiej jak def x = e
nie 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.
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.
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
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
Int => Boolean
znaczy? Myślę, że zdefiniowana składnia todef foo(bar: Baz): Bin = expr