W Internecie jest już wiele fantastycznych odpowiedzi na to pytanie. Napiszę kompilację kilku wyjaśnień i przykładów, które zebrałem na ten temat, na wypadek, gdyby ktoś uznał to za pomocne
WPROWADZENIE
call-by-value (CBV)
Zazwyczaj parametry funkcji są parametrami wywołania według wartości; to znaczy parametry są oceniane od lewej do prawej, aby określić ich wartość przed oszacowaniem samej funkcji
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)
Ale co jeśli musimy napisać funkcję, która przyjmuje jako parametr wyrażenie, którego nie oceniamy, dopóki nie zostanie wywołane w ramach naszej funkcji? W tej sytuacji Scala oferuje parametry nazwy według nazwy. Oznacza to, że parametr jest przekazywany do obecnej funkcji, a jej wycena ma miejsce po zamianie
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
Mechanizm call-by-name przekazuje blok kodu do wywołania i za każdym razem, gdy wywołanie uzyskuje dostęp do parametru, blok kodu jest wykonywany i obliczana jest wartość. W poniższym przykładzie opóźnione drukuje komunikat wykazujący, że metoda została wprowadzona. Następnie opóźnione drukuje komunikat z jego wartością. Wreszcie opóźnione zwroty „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)
}
}
W metodzie opóźnionej
Uzyskanie czasu w nano sekundach
Param: 2027245119786400
Wady i zalety dla każdego przypadku
CBN:
+ Kończy częściej * sprawdź poniżej powyższego zakończenia * + Ma tę zaletę, że argument funkcji nie jest oceniany, jeśli odpowiadający mu parametr nie jest używany w ocenie ciała funkcji - Jest wolniejszy, tworzy więcej klas (co oznacza, że program bierze dłużej, aby załadować) i zużywa więcej pamięci.
CBV:
+ Często jest on wykładniczo bardziej wydajny niż CBN, ponieważ pozwala uniknąć powtórnego obliczania wyrażeń argumentów wywoływanych z nazwy. Ocenia każdy argument funkcji tylko raz + Gra znacznie lepiej z efektami imperatywnymi i efektami ubocznymi, ponieważ zwykle lepiej wiesz, kiedy wyrażenia będą oceniane. -Może to prowadzić do powstania pętli podczas oceny jej parametrów * sprawdź poniżej powyższe zakończenie *
Co zrobić, jeśli wypowiedzenie nie jest gwarantowane?
-Jeżeli ocena CBV wyrażenia e zakończy się, wówczas ocena e CBN e również się zakończy -Inny kierunek nie jest prawdziwy
Przykład braku rozwiązania umowy
def first(x:Int, y:Int)=x
Najpierw rozważ wyrażenie (1, pętla)
CBN: pierwszy (1, pętla) → 1 CBV: pierwszy (1, pętla) → zmniejsz argumenty tego wyrażenia. Ponieważ jedna jest pętlą, nieskończenie redukuje argumenty. To się nie kończy
RÓŻNICE W KAŻDYM ZACHOWANIU SPRAWY
Zdefiniujmy test metody, który będzie
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Test przypadku 1 (2,3)
test(2,3) → 2*2 → 4
Ponieważ zaczynamy od już przeanalizowanych argumentów, będzie to tyle samo kroków dla call-by-value i call-by-name
Test przypadku 2 (3 + 4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
W takim przypadku funkcja call-by-value wykonuje mniej kroków
Test przypadku 3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Unikamy niepotrzebnego obliczenia drugiego argumentu
Test przypadku 4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
Odmienne podejście
Po pierwsze, załóżmy, że mamy funkcję z efektem ubocznym. Ta funkcja drukuje coś, a następnie zwraca wartość Int.
def something() = {
println("calling something")
1 // return value
}
Teraz zdefiniujemy dwie funkcje, które akceptują argumenty Int, które są dokładnie takie same, z wyjątkiem tego, że jedna przyjmuje argument w stylu call-by-value (x: Int), a druga w stylu call-by-name (x: => Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Co się teraz stanie, gdy wywołamy je naszą funkcją uboczną?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Widać więc, że w wersji call-by-value efekt uboczny przekazanego wywołania funkcji (coś ()) wystąpił tylko raz. Jednak w wersji z nazwiskiem efekt uboczny wystąpił dwukrotnie.
Wynika to z tego, że funkcje call-by-value obliczają wartość przekazanego wyrażenia przed wywołaniem funkcji, a zatem ta sama wartość jest dostępna za każdym razem. Jednak funkcje call-by-name przeliczają wartość przekazywanego wyrażenia za każdym razem, gdy jest on uzyskiwany.
PRZYKŁADY, GDZIE LEPIEJ JEST UŻYWANIE WEZWANIA NAZWY
Od: https://stackoverflow.com/a/19036068/1773841
Prosty przykład wydajności: rejestrowanie.
Wyobraźmy sobie taki interfejs:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
A następnie używał w ten sposób:
logger.info("Time spent on X: " + computeTimeSpent)
Jeśli metoda info nic nie robi (ponieważ powiedzmy, że poziom rejestrowania został skonfigurowany na wyższą wartość), wtedy computeTimeSpent nigdy nie zostanie wywołany, co oszczędza czas. Dzieje się tak często w przypadku rejestratorów, w których często widzi się manipulację ciągami, która może być kosztowna w stosunku do rejestrowanych zadań.
Przykład poprawności: operatory logiczne.
Prawdopodobnie widziałeś taki kod:
if (ref != null && ref.isSomething)
Wyobraź sobie, że zadeklarujesz metodę && w następujący sposób:
trait Boolean {
def &&(other: Boolean): Boolean
}
wtedy za każdym razem, gdy ref ma wartość NULL, pojawi się błąd, ponieważ isSomething zostanie wywołany z zerową referencją przed przekazaniem do &&. Z tego powodu faktyczna deklaracja to:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
jest innego typu niżInt
; jest to „funkcja bez argumentów, która wygenerujeInt
„ vs tylkoInt
. Gdy masz już pierwszorzędne funkcje, nie musisz wymyślać terminologii call-by-name, aby to opisać.