Zadzwoń po imieniu i zadzwoń według wartości w Scali, konieczne jest wyjaśnienie

239

Jak rozumiem, w Scali można wywołać funkcję

  • według wartości lub
  • wg nazwy

Na przykład, biorąc pod uwagę następujące deklaracje, czy wiemy, jak zostanie wywołana funkcja?

Deklaracja:

def  f (x:Int, y:Int) = x;

Połączenie

f (1,2)
f (23+55,5)
f (12+3, 44*11)

Jakie są zasady?

James Raitsev
źródło

Odpowiedzi:

540

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ą Intargumenty, 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.

dhg
źródło
296
Zawsze uważałem, że ta terminologia jest niepotrzebnie myląca. Funkcja może mieć wiele parametrów, które różnią się statusem call-by-name vs call-by-value. Więc nie chodzi o to, że funkcja jest wywołana według nazwy lub wywołana przez wartość, lecz o to, że każdy z jej parametrów może być przekazywany według nazwy lub przekazywany według wartości. Ponadto „call-by-name” nie ma nic wspólnego z nazwami . => Intjest innego typu niż Int; jest to „funkcja bez argumentów, która wygeneruje Int„ vs tylko Int. Gdy masz już pierwszorzędne funkcje, nie musisz wymyślać terminologii call-by-name, aby to opisać.
Ben
2
@Ben, to pomaga odpowiedzieć na kilka pytań, dzięki. Chciałbym, żeby więcej opisów wyjaśniało semantykę mijania według nazw.
Christopher Poile,
3
@SelimOber Jeśli tekst f(2)jest skompilowany jako wyrażenie typu Int, wygenerowany kod wywołuje fargument, 2a wynikiem jest wartość wyrażenia. Jeśli ten sam tekst jest kompilowany jako wyrażenie typu, => Intwó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?
Ben
4
@Ben Więc jeśli => Intjest 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 => Intnajwyraźniej nie działa jako typ a val, tylko jako typ parametru.
Tim Goodman
5
@TimGoodman Masz rację, jest to trochę bardziej skomplikowane niż się spodziewałem. => Intjest 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). () => Intjest jawnie funkcją bez argumentów, która zwróci an Int, która musi być wywołana jawnie i może być przekazana jako funkcja. => Intjest swego rodzaju „proxy Int”, a jedyne, co możesz z tym zrobić, to zadzwoń do niego (domyślnie), aby uzyskać Int.
Ben
51

Oto przykład Martina Oderskiego:

def test (x:Int, y: Int)= x*x

Chcemy zbadać strategię oceny i ustalić, która jest szybsza (mniej kroków) w następujących warunkach:

test (2,3)

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.

test (3+4,8)

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.

test (7,2*4)

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

test (3+4, 2*4) 

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.

Behrooz Tabesh
źródło
1
W trzecim przykładzie dla CBV myślę, że miałeś na myśli test (7,8) zamiast testu (7,14)
talonx
1
Przykład pochodzi z Coursera, zasady programowania scala. Wykład 1.2. Wywołanie według nazwy powinno zawierać def test (x:Int, y: => Int) = x * xuwagę, że parametr y nigdy nie jest używany.
dr jerry
1
Dobry przykład! Zaczerpnięte z Coursera MOOC :)
alxsimo,
To dobre wytłumaczenie różnicy, ale nie odnosi się do zadawanych pytań, a mianowicie, które z nich wywołuje Scala
db1234
16

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:

def f(x: => Int, y:Int) = x

W ten sposób parametr xnie będzie oceniany, dopóki nie zostanie wywołany w funkcji.

Ten mały post tutaj również to ładnie wyjaśnia.

resilva87
źródło
10

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ć

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

i bieganie:

scala> callByName(something())
calling something
x1=1
calling something
x2=1

Możesz także napisać:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

I uruchom go w następujący sposób dla tego samego efektu:

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1
użytkownik1496984
źródło
Myślę, że miałeś na myśli: <! - language: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))}, a następnie: <! - language: lang-js -> callAlsoByName (() => coś ()) Nie sądzę, że potrzebujesz nawiasów klamrowych wokół czegoś () w ostatnim wywołaniu. Uwaga: próbowałem po prostu edytować twoją odpowiedź, ale moja edycja została odrzucona przez recenzentów twierdzących, że powinien to być komentarz lub osobna odpowiedź.
lambdista
Najwyraźniej nie można używać podświetlania składni w komentarzach, więc po prostu zignoruj ​​część „<! - language: lang-scala ->”! Zredagowałbym własny komentarz, ale możesz to zrobić tylko w ciągu 5 minut! :)
lambdista,
1
Niedawno też na to wpadłem. Koncepcyjnie można tak myśleć, ale scala rozróżnia między => Ti () => T. Funkcja, która przyjmuje pierwszy typ jako parametr, nie akceptuje drugiego, scala przechowuje wystarczającą ilość informacji w @ScalaSignatureadnotacji, aby zgłosić błąd czasu kompilacji. Kod bajtowy dla obu => Ti () => Tjest taki sam, choć i to Function0. Zobacz to pytanie, aby uzyskać więcej informacji.
vsnyc
6

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:

object main  {

    def main(args: Array[String]) {

        def onTime(time: Long) {
            while(time != time) println("Time to Nag!")
            println("no nags for you!")
        }

        def onRealtime(time: => Long) {
            while(time != time) println("Realtime Nagging executed!")
        }

        onTime(System.nanoTime())
        onRealtime(System.nanoTime())
    }
}

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

guykaplan
źródło
4

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ść.

object Test {
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)
  t
}
}
 1. C: /> scalac Test.scala 
 2. Test Scala
 3. W metodzie opóźnionej
 4. Uzyskiwanie czasu w nano sekundach
 5. Param: 81303808765843
 6. Uzyskiwanie czasu w nano sekundach
sofiene zaghdoudi
źródło
2

Jak zakładam, call-by-valuefunkcja omówiona powyżej przekazuje tylko wartości do funkcji. Według Martin OderskyIt to strategia oceny, po której następuje Scala, która odgrywa ważną rolę w ocenie funkcji. Ale Uprość to call-by-name. jest jak przekazanie funkcji jako argumentu metody znanej również jako Higher-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:

def something() = {
 println("calling something")
 1 // return value
}  

Ta funkcja zawiera jedną printlninstrukcję i zwraca wartość całkowitą. Utwórz funkcję, która ma argumenty jako call-by-name:

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

Ten parametr funkcji definiuje funkcję anonimową, która zwraca jedną wartość całkowitą. W ten xzawiera definicję funkcji, które 0przeszły argumentów ale zwrot intwartości i nasza somethingfunkcja zawierać sam podpis. Kiedy wywołujemy funkcję, przekazujemy funkcję jako argument callByName. Ale w przypadku call-by-valuejego jedynej wartości należy przekazać funkcję do liczby całkowitej. Nazywamy tę funkcję jak poniżej:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

W tej somethingmetodzie nasza metoda wywołała się dwukrotnie, ponieważ gdy uzyskujemy dostęp do wartości metody xin callByName, jej wywołanie do definicji somethingmetody.

Harmeet Singh Taara
źródło
2

Wywołanie według wartości to ogólny przypadek użycia, jak wyjaśniono w wielu odpowiedziach tutaj.

Call-by-name przekazuje blok kodu do wywołującego i za każdym razem, gdy wywołujący uzyskuje dostęp do parametru, blok kodu jest wykonywany i obliczana jest wartość.

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ął.

 /**
   * Executes some code block and prints to stdout the 
time taken to execute   the block 
for interactive testing and debugging.
   */
  def time[T](f: => T): T = {
    val start = System.nanoTime()
    val ret = f
    val end = System.nanoTime()

    println(s"Time taken: ${(end - start) / 1000 / 1000} ms")

    ret
  }

Przykład 2:

iskra apache (ze scala) używa rejestrowania za pomocą call by name, patrz Loggingcecha, w której leniwie ocenia, czy log.isInfoEnabledz poniższej metody.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }
Ram Ghadiyaram
źródło
2

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

object CallbyExample extends App {

  // function definition of call by value
  def CallbyValue(x: Long): Unit = {
    println("The current system time via CBV: " + x);
    println("The current system time via CBV " + x);
  }

  // function definition of call by name
  def CallbyName(x: => Long): Unit = {
    println("The current system time via CBN: " + x);
    println("The current system time via CBN: " + x);
  }

  // function call
  CallbyValue(System.nanoTime());
  println("\n")
  CallbyName(System.nanoTime());
}

Wynik

The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521


The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589

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!

Nijanthan Vijayakumar
źródło
2

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.

val start = Instant.now().toEpochMilli

val calc = (x: Boolean) => {
    Thread.sleep(3000)
    x
}


def callByValue(x: Boolean, y: Boolean): Boolean = {
    if (!x) x else y
}

def callByName(x: Boolean, y: => Boolean): Boolean = {
    if (!x) x else y
}

new Thread(() => {
    println("========================")
    println("Call by Value " + callByValue(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


new Thread(() => {
    println("========================")
    println("Call by Name " + callByName(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


Thread.sleep(5000)

Dane wyjściowe kodu będą następujące:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================
Madpoptart
źródło
1

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.

// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1

// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)

// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))

// will not terminate, since loop(2) will evaluate. 
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 
iblamefish
źródło
1

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*24

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 * 749
call-by-name: (3+4)*(3+4)7 * (3+4)7 * 749

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 * 749
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 * 749
call-by-name: (3+4)*(3+4)7*(3+4)7*749

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
}
Ignacio Alorre
źródło
1

Przejrzenie przykładu powinno pomóc ci lepiej zrozumieć różnicę.

Zdefiniujmy prostą funkcję, która zwraca bieżący czas:

def getTime = System.currentTimeMillis

Teraz zdefiniujemy funkcję według nazwy , która drukuje dwa razy z opóźnieniem o sekundę:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

I jeden pod względem wartości :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

Teraz zadzwońmy do każdego:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

Wynik powinien wyjaśnić różnicę. Fragment jest dostępny tutaj .

Maroun
źródło
0

CallByNamejest wywoływany, gdy jest używany, i callByValuejest 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 scalamonitu.

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

callByNamefunkcja przyjmuje wyżej loopmetody jako argument i to nigdy nie jest używany wewnątrz jego ciała.

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

Podczas wykonywania callByNamemetody nie znajdujemy żadnego problemu (otrzymujemy scalamonit z powrotem), ponieważ nie jesteśmy w żadnym miejscu przy użyciu funkcji pętli wewnątrz callByNamefunkcji.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

callByValuefunkcja przyjmuje wyżej loopmetody jako parametr w wyniku wewnątrz funkcji lub wyrażenie jest oceniane przed wykonaniem funkcji zewnętrznej tam loopfunkcji zawartej rekurencyjnie i nigdy nie uzyskać scalaszybką plecy.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))
Puneeth Reddy V.
źródło
0

Zobacz:

    object NameVsVal extends App {

  def mul(x: Int, y: => Int) : Int = {
    println("mul")
    x * y
  }
  def add(x: Int, y: Int): Int = {
    println("add")
    x + y
  }
  println(mul(3, add(2, 1)))
}

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.

Apurva Singh
źródło
-2

Nie sądzę, aby wszystkie odpowiedzi tutaj zawierały prawidłowe uzasadnienie:

W wywołaniu według wartości argumenty są obliczane tylko raz:

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

widać powyżej, że wszystkie argumenty są sprawdzane, czy nie są potrzebne, zwykle call-by-valuemoże być szybki, ale nie zawsze tak jak w tym przypadku.

Gdyby strategia oceny była, call-by-namewówczas rozkład byłby:

f(12 + 3, 4 * 11)
12 + 3
15

jak widać powyżej, nigdy nie musieliśmy oceniać 4 * 11i dlatego zaoszczędziliśmy trochę obliczeń, co może być czasem korzystne.

vivek
źródło