W podanym przykładzie użyto tylko funkcji call-by-value, więc dam nowy, prostszy przykład, który pokazuje różnicę.
Po pierwsze, załóżmy, że mamy funkcję z efektem ubocznym. Ta funkcja drukuje coś, a następnie zwraca an Int
.
def something() = {
println("calling something")
1 // return value
}
Teraz zdefiniujemy dwie funkcje, które akceptują Int
argumenty, 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 przekazanej funkcji call ( something()
) 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. Zamiast tego funkcje wywołania według nazwy przeliczają wartość przekazywanego wyrażenia za każdym razem, gdy jest on uzyskiwany.
=> 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ć.f(2)
jest skompilowany jako wyrażenie typuInt
, wygenerowany kod wywołujef
argument,2
a wynikiem jest wartość wyrażenia. Jeśli ten sam tekst jest kompilowany jako wyrażenie typu,=> Int
wówczas wygenerowany kod wykorzystuje odniesienie do pewnego rodzaju „bloku kodu” jako wartości wyrażenia. Tak czy inaczej, wartość tego typu można przekazać do funkcji oczekującej parametru tego typu. Jestem prawie pewien, że możesz to zrobić ze zmiennym przypisaniem, bez widocznego parametru. Więc co mają z tym wspólnego imiona i nazwiska?=> Int
jest to „funkcja bez argumentów, która generuje Int”, to czym ona się różni() => Int
? Scala wydaje się traktować je inaczej, na przykład=> Int
najwyraźniej nie działa jako typ aval
, tylko jako typ parametru.=> Int
jest wygodą i nie jest zaimplementowany dokładnie tak, jak obiekt funkcji (przypuszczalnie dlaczego nie możesz mieć zmiennych typu=> Int
, chociaż nie ma podstawowego powodu, dla którego to nie działałoby).() => Int
jest jawnie funkcją bez argumentów, która zwróci anInt
, która musi być wywołana jawnie i może być przekazana jako funkcja.=> Int
jest swego rodzaju „proxyInt
”, a jedyne, co możesz z tym zrobić, to zadzwoń do niego (domyślnie), aby uzyskaćInt
.Oto przykład Martina Oderskiego:
Chcemy zbadać strategię oceny i ustalić, która jest szybsza (mniej kroków) w następujących warunkach:
call by value: test (2,3) -> 2 * 2 -> 4
call by name: test (2,3) -> 2 * 2 -> 4
Tutaj osiąga się wynik z taką samą liczbą kroków.
połączenie według wartości: test (7,8) -> 7 * 7 -> 49
połączenie według nazwy: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Połączenie tutaj pod względem wartości jest szybszy.
połączenie według wartości: test (7,8) -> 7 * 7 -> 49
połączenie według nazwy: 7 * 7 -> 49
Tutaj połączenie według nazwy jest szybsze
połączenie według wartości: test (7,2 * 4) -> test (7, 8) -> 7 * 7 -> 49
połączenie według nazwy: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Wynik osiąga się w tych samych krokach.
źródło
def test (x:Int, y: => Int) = x * x
uwagę, że parametr y nigdy nie jest używany.W twoim przykładzie wszystkie parametry zostaną ocenione przed wywołaniem w funkcji, ponieważ definiujesz je tylko według wartości . Jeśli chcesz zdefiniować parametry według nazwy , powinieneś przekazać blok kodu:
W ten sposób parametr
x
nie będzie oceniany, dopóki nie zostanie wywołany w funkcji.Ten mały post tutaj również to ładnie wyjaśnia.
źródło
Aby powtórzyć punkt @ Bena w powyższych komentarzach, myślę, że najlepiej myśleć o „call-by-name” jako po prostu cukrze syntaktycznym. Analizator składni po prostu otacza wyrażenia funkcjami anonimowymi, aby można je było wywołać w późniejszym czasie, gdy zostaną użyte.
W efekcie zamiast definiować
i bieganie:
Możesz także napisać:
I uruchom go w następujący sposób dla tego samego efektu:
źródło
=> T
i() => T
. Funkcja, która przyjmuje pierwszy typ jako parametr, nie akceptuje drugiego, scala przechowuje wystarczającą ilość informacji w@ScalaSignature
adnotacji, aby zgłosić błąd czasu kompilacji. Kod bajtowy dla obu=> T
i() => T
jest taki sam, choć i toFunction0
. Zobacz to pytanie, aby uzyskać więcej informacji.Spróbuję wyjaśnić prostym przypadkiem użycia, a nie podając tylko przykład
Wyobraź sobie, że chcesz zbudować „aplikację dokuczliwą”, która będzie Cię nękać za każdym razem, gdy ostatni raz będziesz dręczony.
Sprawdź następujące implementacje:
W powyższej implementacji nagger będzie działał tylko przy przekazywaniu według nazwy, ponieważ podczas przekazywania według wartości będzie on ponownie użyty, a zatem wartość nie zostanie ponownie oszacowana, a przy przejściu według nazwy wartość będzie ponownie oceniana co czas dostępu do zmiennych
źródło
Zazwyczaj parametry funkcji są parametrami według wartości; to znaczy wartość parametru jest określana przed przekazaniem go do funkcji. Ale co jeśli musimy napisać funkcję, która akceptuje jako parametr wyrażenie, którego nie chcemy oceniać, dopóki nie zostanie wywołane w ramach naszej funkcji? W tej sytuacji Scala oferuje parametry nazwy według nazwy.
Mechanizm call-by-name przekazuje blok kodu do odbiorcy i za każdym razem, gdy odbiorca uzyskuje dostęp do parametru, blok kodu jest wykonywany i obliczana jest wartość.
źródło
Jak zakładam,
call-by-value
funkcja omówiona powyżej przekazuje tylko wartości do funkcji. WedługMartin Odersky
It to strategia oceny, po której następuje Scala, która odgrywa ważną rolę w ocenie funkcji. Ale Uprość tocall-by-name
. jest jak przekazanie funkcji jako argumentu metody znanej również jakoHigher-Order-Functions
. Gdy metoda uzyskuje dostęp do wartości przekazanego parametru, wywołuje implementację przekazanych funkcji. jak poniżej:Zgodnie z przykładem @dhg utwórz metodę najpierw jako:
Ta funkcja zawiera jedną
println
instrukcję i zwraca wartość całkowitą. Utwórz funkcję, która ma argumenty jakocall-by-name
:Ten parametr funkcji definiuje funkcję anonimową, która zwraca jedną wartość całkowitą. W ten
x
zawiera definicję funkcji, które0
przeszły argumentów ale zwrotint
wartości i naszasomething
funkcja zawierać sam podpis. Kiedy wywołujemy funkcję, przekazujemy funkcję jako argumentcallByName
. Ale w przypadkucall-by-value
jego jedynej wartości należy przekazać funkcję do liczby całkowitej. Nazywamy tę funkcję jak poniżej:W tej
something
metodzie nasza metoda wywołała się dwukrotnie, ponieważ gdy uzyskujemy dostęp do wartości metodyx
incallByName
, jej wywołanie do definicjisomething
metody.źródło
Wywołanie według wartości to ogólny przypadek użycia, jak wyjaśniono w wielu odpowiedziach tutaj.
Postaram się zademonstrować połączenie według nazwy w prostszy sposób, korzystając z poniższych przypadków użycia
Przykład 1:
Prosty przykład / przypadek użycia wywołania według nazwy znajduje się poniżej funkcji, która przyjmuje funkcję jako parametr i podaje czas, który upłynął.
Przykład 2:
iskra apache (ze scala) używa rejestrowania za pomocą call by name, patrz
Logging
cecha, w której leniwie ocenia, czylog.isInfoEnabled
z poniższej metody.źródło
W wywołaniu według wartości wartość wyrażenia jest wstępnie obliczana w momencie wywołania funkcji i ta konkretna wartość jest przekazywana jako parametr do odpowiedniej funkcji. Ta sama wartość będzie używana w całej funkcji.
Podczas gdy w wywołaniu według nazwy samo wyrażenie jest przekazywane jako parametr do funkcji i jest obliczane tylko wewnątrz funkcji, ilekroć ten konkretny parametr zostanie wywołany.
Różnicę między wywołaniem według nazwy i wywołaniem według wartości w Scali można lepiej zrozumieć na poniższym przykładzie:
Fragment kodu
Wynik
W powyższym fragmencie kodu dla wywołania funkcji CallbyValue (System.nanoTime ()) systemowy czas nano jest wstępnie obliczany, a ta wstępnie obliczona wartość została przekazana parametrowi do wywołania funkcji.
Ale w wywołaniu funkcji CallbyName (System.nanoTime ()) samo wyrażenie „System.nanoTime ())” jest przekazywane jako parametr do wywołania funkcji, a wartość tego wyrażenia jest obliczana, gdy parametr ten jest używany wewnątrz funkcji .
Zwróć uwagę na definicję funkcji CallbyName, gdzie występuje symbol => oddzielający parametr x i jego typ danych. Ten konkretny symbol wskazuje, że funkcja jest wywoływana według typu nazwy.
Innymi słowy, argumenty funkcji wywołania według wartości są oceniane raz przed wejściem do funkcji, ale argumenty funkcji wywołania według nazwy są oceniane w funkcji tylko wtedy, gdy są potrzebne.
Mam nadzieję że to pomoże!
źródło
Oto krótki przykład, który zakodowałem, aby pomóc mojemu koledze, który obecnie bierze udział w kursie Scala. To, co uważałem za interesujące, to fakt, że Martin nie wykorzystał jako odpowiedzi pytania z pytaniami && przedstawionymi wcześniej w wykładzie. W każdym razie mam nadzieję, że to pomoże.
Dane wyjściowe kodu będą następujące:
źródło
Parametry są zwykle przekazywane przez wartość, co oznacza, że zostaną ocenione przed zastąpieniem ich w treści funkcji.
Można wymusić wywołanie parametru według nazwy, używając podwójnej strzałki podczas definiowania funkcji.
źródło
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
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
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”:
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
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
Test przypadku 1 (2,3)
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)
W takim przypadku funkcja call-by-value wykonuje mniej kroków
Test przypadku 3 (7, 2 * 4)
Unikamy niepotrzebnego obliczenia drugiego argumentu
Test przypadku 4 (3 + 4, 2 * 4)
Odmienne podejście
Po pierwsze, załóżmy, że mamy funkcję z efektem ubocznym. Ta funkcja drukuje coś, a następnie zwraca wartość Int.
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).
Co się teraz stanie, gdy wywołamy je naszą funkcją uboczną?
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:
A następnie używał w ten sposób:
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:
Wyobraź sobie, że zadeklarujesz metodę && w następujący sposób:
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:
źródło
Przejrzenie przykładu powinno pomóc ci lepiej zrozumieć różnicę.
Zdefiniujmy prostą funkcję, która zwraca bieżący czas:
Teraz zdefiniujemy funkcję według nazwy , która drukuje dwa razy z opóźnieniem o sekundę:
I jeden pod względem wartości :
Teraz zadzwońmy do każdego:
Wynik powinien wyjaśnić różnicę. Fragment jest dostępny tutaj .
źródło
CallByName
jest wywoływany, gdy jest używany, icallByValue
jest wywoływany za każdym razem, gdy napotkamy instrukcję.Na przykład:-
Mam nieskończoną pętlę, tzn. Jeśli wykonasz tę funkcję, nigdy nie otrzymamy
scala
monitu.callByName
funkcja przyjmuje wyżejloop
metody jako argument i to nigdy nie jest używany wewnątrz jego ciała.Podczas wykonywania
callByName
metody nie znajdujemy żadnego problemu (otrzymujemyscala
monit z powrotem), ponieważ nie jesteśmy w żadnym miejscu przy użyciu funkcji pętli wewnątrzcallByName
funkcji.callByValue
funkcja przyjmuje wyżejloop
metody jako parametr w wyniku wewnątrz funkcji lub wyrażenie jest oceniane przed wykonaniem funkcji zewnętrznej tamloop
funkcji zawartej rekurencyjnie i nigdy nie uzyskaćscala
szybką plecy.źródło
Zobacz:
y: => Int to wywołanie według nazwy. To, co przekazywane jest jako call by name, to add (2, 1). Zostanie to ocenione leniwie. Tak więc na konsoli pojawi się „mul”, a następnie „add”, chociaż wydaje się, że add jest wywoływany jako pierwszy. Wywołanie według nazwy działa jak przekazanie wskaźnika funkcji.
Teraz zmień z y: => Int na y: Int. Konsola wyświetli „dodaj”, a następnie „mul”! Zwykły sposób oceny.
źródło
Nie sądzę, aby wszystkie odpowiedzi tutaj zawierały prawidłowe uzasadnienie:
W wywołaniu według wartości argumenty są obliczane tylko raz:
widać powyżej, że wszystkie argumenty są sprawdzane, czy nie są potrzebne, zwykle
call-by-value
może być szybki, ale nie zawsze tak jak w tym przypadku.Gdyby strategia oceny była,
call-by-name
wówczas rozkład byłby:jak widać powyżej, nigdy nie musieliśmy oceniać
4 * 11
i dlatego zaoszczędziliśmy trochę obliczeń, co może być czasem korzystne.źródło