web-dev-qa-db-de.com

Warum ist Lazy Evaluation nützlich?

Ich habe mich schon lange gefragt, warum eine faule Bewertung nützlich ist. Ich muss noch jemanden auf eine sinnvolle Weise erklären. Meistens endet es mit dem "Vertrauen".

Hinweis: Ich meine nicht Memoisierung. 

110
Joel McCracken

Meistens weil es effizienter sein kann - Werte müssen nicht berechnet werden, wenn sie nicht verwendet werden. Zum Beispiel kann ich drei Werte an eine Funktion übergeben, aber abhängig von der Reihenfolge der Bedingungsausdrücke kann tatsächlich nur eine Teilmenge verwendet werden. In einer Sprache wie C werden alle drei Werte trotzdem berechnet. In Haskell werden jedoch nur die erforderlichen Werte berechnet.

Es erlaubt auch coole Sachen wie unendliche Listen. Ich kann keine unendliche Liste in einer Sprache wie C haben, aber in Haskell ist das kein Problem. Unbegrenzte Listen werden in bestimmten Bereichen der Mathematik ziemlich häufig verwendet, daher kann es nützlich sein, sie manipulieren zu können.

90
mipadi

Ein nützliches Beispiel für eine langsame Bewertung ist die Verwendung von quickSort:

quickSort [] = []
quickSort (x:xs) = quickSort (filter (< x) xs) ++ [x] ++ quickSort (filter (>= x) xs)

Wenn wir nun das Minimum der Liste finden wollen, können wir definieren

minimum ls = head (quickSort ls)

Die Sortiert zuerst die Liste und übernimmt dann das erste Element der Liste. Wegen der langsamen Bewertung wird jedoch nur der Kopf berechnet. Wenn wir beispielsweise das Minimum der Liste annehmen, filtert [2, 1, 3,] quickSort zuerst alle Elemente heraus, die kleiner als zwei sind. Dann macht es quickSort auf das (die Singleton-Liste [1] zurückgibt), was schon genug ist. Aufgrund der langsamen Bewertung wird der Rest nie sortiert, wodurch viel Rechenzeit gespart wird.

Dies ist natürlich ein sehr einfaches Beispiel, aber Faulheit funktioniert bei Programmen, die sehr groß sind, auf dieselbe Weise.

Es gibt jedoch einen Nachteil bei all dem: Es wird schwieriger, die Laufzeitgeschwindigkeit und den Speicherbedarf Ihres Programms vorherzusagen. Das bedeutet nicht, dass faule Programme langsamer sind oder mehr Speicher benötigen, aber es ist gut zu wissen.

69
Chris Eidhof

Ich finde eine faule Bewertung für eine Reihe von Dingen nützlich. 

Erstens sind alle vorhandenen Lazy-Sprachen rein, weil es in einer Lazy-Sprache sehr schwierig ist, über Nebenwirkungen nachzudenken.

In reinen Sprachen können Sie über Funktionsdefinitionen nach dem Gleichheitsgrund denken.

foo x = x + 3

In einer nicht faulen Umgebung können leider nicht mehr Anweisungen zurückgegeben werden als in einer faulen Umgebung. Daher ist dies in Sprachen wie ML weniger nützlich. Aber in einer faulen Sprache kann man sicher über Gleichheit sprechen.

Zweitens: In faule Sprachen wie Haskell werden viele Dinge wie die 'Werteinschränkung' in ML nicht benötigt. Dies führt zu einem großen Dekuttern der Syntax. ML-ähnliche Sprachen müssen Schlüsselwörter wie var oder Spaß verwenden. In Haskell fallen diese Dinge zu einer Vorstellung zusammen.

Drittens können Sie mit Faulheit sehr funktionalen Code schreiben, der in Stücke verstanden werden kann. In Haskell ist es üblich, einen Funktionskörper wie folgt zu schreiben:

foo x y = if condition1
          then some (complicated set of combinators) (involving bigscaryexpression)
          else if condition2
          then bigscaryexpression
          else Nothing
  where some x y = ...
        bigscaryexpression = ...
        condition1 = ...
        condition2 = ...

Auf diese Weise können Sie durch das Verständnis des Körpers einer Funktion "von oben" arbeiten. ML-ähnliche Sprachen zwingen Sie zur Verwendung eines streng bewerteten Let. Folglich wagen Sie es nicht, die let-Klausel in den Hauptteil der Funktion zu "heben", denn wenn sie teuer ist (oder Nebenwirkungen hat), möchten Sie nicht, dass sie immer ausgewertet wird. Haskell kann die Details explizit in die where-Klausel verschieben, da sie weiß, dass der Inhalt dieser Klausel nur bei Bedarf ausgewertet wird.

In der Praxis neigen wir dazu, Schutzeinrichtungen zu verwenden und dies weiter zu reduzieren:

foo x y 
  | condition1 = some (complicated set of combinators) (involving bigscaryexpression)
  | condition2 = bigscaryexpression
  | otherwise  = Nothing
  where some x y = ...
        bigscaryexpression = ...
        condition1 = ...
        condition2 = ...

Viertens bietet Faulheit manchmal einen viel eleganteren Ausdruck bestimmter Algorithmen. Eine faule "schnelle Sortierung" in Haskell ist ein Einzeiler und hat den Vorteil, dass, wenn Sie nur die ersten Elemente betrachten, Sie nur Kosten zahlen, die proportional zu den Kosten für die Auswahl dieser Elemente sind. Nichts hindert Sie daran, dies streng zu tun, aber Sie müssen den Algorithmus wahrscheinlich jedes Mal neu codieren, um die gleiche asymptotische Leistung zu erzielen.

Fünftens können Sie mit Faulheit neue Kontrollstrukturen in der Sprache definieren. Sie können kein neues "Wenn ... Dann ... Andern ..." wie Konstrukt in einer strengen Sprache schreiben. Wenn Sie versuchen, eine Funktion wie folgt zu definieren:

if' True x y = x
if' False x y = y

in einer strengen Sprache würden dann beide Zweige unabhängig vom Zustandswert ausgewertet. Es wird schlimmer, wenn Sie Schleifen betrachten. Alle strengen Lösungen erfordern die Sprache, um Ihnen eine Art Angebot oder explizite Lambda-Konstruktion zu liefern.

Schließlich können auf diese Weise einige der besten Mechanismen für den Umgang mit Nebenwirkungen im Typensystem wie Monaden tatsächlich nur in einer trägen Umgebung effektiv zum Ausdruck gebracht werden. Dies kann durch einen Vergleich der Komplexität von F # 's Workflows mit Haskell Monads beobachtet werden. (Sie können eine Monade in einer strengen Sprache definieren, aber leider werden Sie oft ein oder zwei Monadegesetze verfehlen, weil es an Faulheit und Workflows fehlt.

62
Edward KMETT

Es gibt einen Unterschied zwischen der Bewertung normaler Ordnung und einer faulen Bewertung (wie in Haskell).

square x = x * x

Den folgenden Ausdruck auswerten ...

square (square (square 2))

... mit eifriger Bewertung:

> square (square (2 * 2))
> square (square 4)
> square (4 * 4)
> square 16
> 16 * 16
> 256

... bei normaler Auftragsauswertung:

> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * (square (square 2))
> ((2 * 2) * (square 2)) * (square (square 2))
> (4 * (square 2)) * (square (square 2))
> (4 * (2 * 2)) * (square (square 2))
> (4 * 4) * (square (square 2))
> 16 * (square (square 2))
> ...
> 256

... mit fauler Bewertung:

> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * ((square 2) * (square 2))
> ((2 * 2) * (2 * 2)) * ((2 * 2) * (2 * 2))
> (4 * 4) * (4 * 4)
> 16 * 16
> 256

Das liegt daran, dass Lazy Evaluation den Syntaxbaum betrachtet und Baumtransformationen durchführt ...

square (square (square 2))

           ||
           \/

           *
          / \
          \ /
    square (square 2)

           ||
           \/

           *
          / \
          \ /
           *
          / \
          \ /
        square 2

           ||
           \/

           *
          / \
          \ /
           *
          / \
          \ /
           *
          / \
          \ /
           2

... während bei der normalen Auftragsbewertung nur Textausdehnungen vorgenommen werden.

Deshalb werden wir, wenn Sie eine langsame Bewertung verwenden, leistungsfähiger (die Bewertung endet häufiger als andere Strategien), während die Leistung einer eifrigen Bewertung (zumindest in der O-Notation) entspricht.

28
Thomas Danecker

Eine faule Bewertung in Bezug auf die CPU ist genauso wie die Garbage Collection in Bezug auf den Arbeitsspeicher. Mit GC können Sie so tun, als hätten Sie unbegrenzt viel Speicher und fordern so viele Objekte im Speicher an, wie Sie benötigen. Runtime stellt unbrauchbare Objekte automatisch wieder her. Mit LE können Sie so tun, als hätten Sie unbegrenzte Rechenressourcen - Sie können so viele Berechnungen durchführen, wie Sie benötigen. Die Laufzeit führt einfach keine (für den gegebenen Fall) unnötigen Berechnungen aus.

Was ist der praktische Vorteil dieser "vorgeblichen" Modelle? Es befreit Entwickler (bis zu einem gewissen Grad) von der Verwaltung von Ressourcen und entfernt bestimmten Code aus den Quellen. Wichtiger ist jedoch, dass Sie Ihre Lösung effizient in weiteren Zusammenhängen wiederverwenden können.

Stellen Sie sich vor, Sie haben eine Liste von Zahlen S und eine Zahl N. Sie müssen die Nummer N in der Nähe von Nummer M aus der Liste S suchen. Sie können zwei Kontexte haben: ein einzelnes N und eine Liste L von Ns (ei für jedes N in L Sie suchen das nächste M in S). Wenn Sie Lazy Evaluation verwenden, können Sie S sortieren und binäre Suche anwenden, um M am nächsten zu finden. Für eine gute Lazy-Sortierung sind O(size(S)) Schritte für einzelnes N und O (ln (size ( S)) * (Größe (S) + Größe (L))) Schritte für gleichmäßig verteilte L-Werte. Wenn Sie keine verzögerte Bewertung haben, um die optimale Effizienz zu erreichen, müssen Sie einen Algorithmus für jeden Kontext implementieren.

25
Alexey

Wenn Sie Simon Peyton Jones glauben, ist eine faule Bewertung per se nicht wichtig , sondern nur als „Haarhemd“, das die Designer dazu zwang, die Sprache rein zu halten. Ich finde diesen Standpunkt sympathisch.

Richard Bird, John Hughes und in geringerem Maße Ralf Hinze sind in der Lage, mit fauler Bewertung erstaunliche Dinge zu tun. Das Lesen ihrer Arbeit wird Ihnen dabei helfen, sie zu schätzen. Zu guten Ausgangspunkten gehören Birds großartiges Sudok Solver und Hughes 'Artikel Why Functional Programming Matters .

25
Norman Ramsey

Betrachten Sie ein Tic-Tac-Toe-Programm. Dies hat vier Funktionen:

  • Eine Funktion zum Generieren einer Bewegung, die eine aktuelle Karte übernimmt und eine Liste neuer Karten mit jeweils einer Zugbewegung generiert.
  • Dann gibt es eine "Move-Tree" -Funktion, die die Move-Generierungsfunktion anwendet, um alle möglichen Board-Positionen abzuleiten, die sich daraus ergeben könnten. 
  • Es gibt eine Minimax-Funktion, die den Baum (oder möglicherweise nur einen Teil davon) durchläuft, um die beste nächste Bewegung zu finden.
  • Es gibt eine Board-Evaluierungsfunktion, die bestimmt, ob einer der Spieler gewonnen hat.

Dies schafft eine klare Trennung der Bedenken von Nizza. Insbesondere die Move-Generierungsfunktion und die Board-Evaluierungsfunktionen sind die einzigen, die die Spielregeln verstehen müssen: Die Move-Tree- und Minimax-Funktionen sind vollständig wiederverwendbar.

Versuchen wir nun, Schach anstelle von Tic-Tac-Toe zu implementieren. In einer "eifrigen" (d. H. Konventionellen) Sprache funktioniert dies nicht, da der Bewegungsbaum nicht in den Speicher passt. Nun müssen die Board-Auswertungs- und Bewegungsgenerierungsfunktionen mit dem Bewegungsbaum und der Minimax-Logik gemischt werden, da die Minimax-Logik verwendet werden muss, um zu entscheiden, welche Züge erzeugt werden sollen. Unser Nice clean modularer Aufbau verschwindet.

In einer Lazy-Sprache werden die Elemente des Bewegungsbaums jedoch nur als Reaktion auf die Anforderungen der Minimax-Funktion generiert: Der gesamte Bewegungsbaum muss nicht generiert werden, bevor sich Minimax vom obersten Element löst. Unsere saubere modulare Struktur funktioniert also immer noch in einem echten Spiel.

13
Paul Johnson

Hier sind zwei weitere Punkte, von denen ich glaube, dass sie in der Diskussion noch nicht angesprochen wurden.

  1. Faulheit ist ein Synchronisationsmechanismus in einer gleichzeitigen Umgebung. Es ist eine einfache und einfache Möglichkeit, einen Verweis auf einige Berechnungen zu erstellen und die Ergebnisse für viele Threads bereitzustellen. Wenn mehrere Threads versuchen, auf einen unausgewerteten Wert zuzugreifen, wird sie nur von einem Thread ausgeführt, und die anderen werden entsprechend blockiert und erhalten den Wert, sobald er verfügbar ist.

  2. Faulheit ist für die Amortisierung von Datenstrukturen in einer reinen Umgebung von grundlegender Bedeutung. Okasaki beschreibt dies ausführlich in Rein funktionale Datenstrukturen , aber die grundlegende Idee ist, dass Lazy Evaluation eine kontrollierte Form von Mutationen ist, die für die effiziente Implementierung bestimmter Arten von Datenstrukturen wichtig ist. Während wir oft von Faulheit sprechen, die uns zwingt, das Reinheitshemd zu tragen, gilt auch der andere Weg: Es handelt sich um ein Paar synergistischer Sprachmerkmale.

12
Edward Z. Yang

Wenn Sie Ihren Computer einschalten und Windows nicht jedes einzelne Verzeichnis auf Ihrer Festplatte in Windows Explorer öffnen und jedes einzelne auf Ihrem Computer installierte Programm starten, bis Sie angeben, dass ein bestimmtes Verzeichnis oder ein bestimmtes Programm benötigt wird ist "faul" bewertung.

Die "faule" Auswertung führt Operationen aus, wenn und wie sie benötigt werden. Dies ist nützlich, wenn es sich um eine Funktion einer Programmiersprache oder Bibliothek handelt, da es in der Regel schwieriger ist, Lazy Evaluation selbst zu implementieren, als einfach alles vorher vorausberechnen.

9
yfeldblum
  1. Es kann die Effizienz steigern. Dies ist die offensichtlich aussehende, aber es ist nicht wirklich die wichtigste. (Beachten Sie auch, dass Faulheit töten Effizienz auch - diese Tatsache ist nicht sofort offensichtlich. Indem Sie jedoch viele temporäre Ergebnisse speichern, anstatt sie sofort zu berechnen, können Sie eine große Menge an RAM verbrauchen.)

  2. Hiermit können Sie Ablaufsteuerungskonstrukte in normalem Code auf Benutzerebene definieren, anstatt sie fest in der Sprache zu codieren. (ZB Java hat for Schleifen; Haskell hat eine for Funktion. Java hat Ausnahmebehandlung; Haskell hat verschiedene Arten von Ausnahmemonaden. C # hat goto; Haskell hat die Fortsetzung Monade ...)

  3. Sie können den Algorithmus für Generieren Daten vom Algorithmus für Wie viel zu generierende Daten entkoppeln. Sie können eine Funktion schreiben, die eine fiktive Endlosliste der Ergebnisse generiert, und eine andere Funktion, die so viel von dieser Liste verarbeitet, wie sie benötigt. Genauer gesagt, Sie können fünf Generatorfunktionen und fünf Consumerfunktionen verwenden und jede Kombination effizient erstellen - anstatt 5 x 5 = 25 kombinierte Funktionen manuell zu codieren beide Aktionen gleichzeitig. (!) Wir alle wissen, dass Entkopplung eine gute Sache ist.

  4. Es zwingt Sie mehr oder weniger dazu, eine reine funktionale Sprache zu entwerfen. Es ist immer verlockend, Abkürzungen zu nehmen, aber in einer faulen Sprache macht die geringste Verunreinigung Ihren Code wild unvorhersehbar, was stark gegen das Nehmen von Abkürzungen spricht.

8

Ein großer Vorteil von Faulheit ist die Fähigkeit, unveränderliche Datenstrukturen mit angemessenen amortisierten Grenzen zu schreiben. Ein einfaches Beispiel ist ein unveränderlicher Stapel (mit F #):

type 'a stack =
    | EmptyStack
    | StackNode of 'a * 'a stack

let rec append x y =
    match x with
    | EmptyStack -> y
    | StackNode(hd, tl) -> StackNode(hd, append tl y)

Der Code ist vernünftig, aber das Anhängen von zwei Stapeln x und y benötigt in den besten, schlechtesten und durchschnittlichen Fällen O (Länge von x). Das Anhängen von zwei Stapeln ist eine monolithische Operation. Sie berührt alle Knoten im Stapel x.

Wir können die Datenstruktur als Lazy Stack umschreiben:

type 'a lazyStack =
    | StackNode of Lazy<'a * 'a lazyStack>
    | EmptyStack

let rec append x y =
    match x with
    | StackNode(item) -> Node(lazy(let hd, tl = item.Force(); hd, append tl y))
    | Empty -> y

lazy bewirkt, dass die Bewertung von Code in seinem Konstruktor ausgesetzt wird. Nach der Auswertung mit .Force() wird der Rückgabewert zwischengespeichert und bei jedem nachfolgenden .Force() wiederverwendet.

Bei der Lazy-Version handelt es sich bei Anhängen um eine Operation O(1): Sie gibt einen Knoten zurück und setzt die erneute Erstellung der Liste aus. Wenn Sie den Kopf dieser Liste erhalten, wertet er den Inhalt des Knotens aus, zwingt ihn dazu, den Kopf zurückzugeben, und erstellt eine Aufhängung mit den übrigen Elementen, sodass der Kopf der Liste eine O(1) - Operation ist .

Unsere faule Liste befindet sich also ständig im Wiederaufbau. Sie zahlen die Kosten für die Neuerstellung dieser Liste erst, wenn Sie alle ihre Elemente durchlaufen haben. Bei Verwendung von Faulheit unterstützt diese Liste das Konsensieren und Anhängen von O(1). Interessanterweise ist es absolut möglich, eine Liste mit potenziell unendlichen Elementen zu erstellen, da Knoten erst beim Zugriff auf Knoten ausgewertet werden.

Die Datenstruktur oben erfordert nicht, dass Knoten bei jedem Durchlauf neu berechnet werden. Sie unterscheiden sich daher deutlich von Vanilla IEnumerables in .NET.

6
Juliet

Bedenken Sie:

if (conditionOne && conditionTwo) {
  doSomething();
}

Die Methode doSomething () wird nur ausgeführt, wenn conditionOne auf true und conditionTwo auf true gesetzt ist ..__ Wenn conditionOne auf false gesetzt ist, warum müssen Sie das Ergebnis von conditionTwo berechnen? Die Bewertung von conditionTwo ist in diesem Fall eine Zeitverschwendung, insbesondere wenn Ihre Bedingung das Ergebnis eines Methodenprozesses ist.

Das ist ein Beispiel für das faule Bewertungsinteresse ...

6
Romain Linsolas

Bei Datenstrukturen ist eine faule Auswertung am nützlichsten. Sie können ein Array oder einen Vektor definieren, indem Sie nur bestimmte Punkte in der Struktur induktiv angeben und alle anderen als das gesamte Array ausdrücken. Dadurch können Sie Datenstrukturen sehr präzise und mit hoher Laufzeitleistung generieren.

Um dies in Aktion zu sehen, können Sie einen Blick auf meine Bibliothek für neuronale Netzwerke mit dem Namen instinct werfen. Es nutzt faul Bewertungen für Eleganz und hohe Leistung. Zum Beispiel werde ich die traditionell zwingende Aktivierungsberechnung völlig los. Ein einfacher, fauler Ausdruck tut alles für mich.

Dies wird zum Beispiel in der Aktivierungsfunktion und auch im Backpropagation-Lernalgorithmus verwendet (ich kann nur zwei Links posten, daher müssen Sie die learnPat-Funktion im AI.Instinct.Train.Delta-Modul selbst nachschlagen). Herkömmlicherweise erfordern beide viel kompliziertere iterative Algorithmen.

5
ertes

Andere Leute gaben bereits alle wichtigen Gründe an, aber ich denke, eine nützliche Übung, um zu verstehen, warum Faulheit wichtig ist, besteht darin, eine Fixpunkt - Funktion in einer strengen Sprache zu schreiben.

In Haskell ist eine Festkomma-Funktion extrem einfach:

fix f = f (fix f)

das dehnt sich auf aus

f (f (f ....

aber da Haskell faul ist, ist diese unendliche Berechnungskette kein Problem. Die Bewertung erfolgt "von außen nach innen" und alles funktioniert wunderbar:

fact = fix $ \f n -> if n == 0 then 1 else n * f (n-1)

Wichtig ist nicht, dass fix faul ist, sondern dass f faul ist. Wenn Sie bereits eine strikte f erhalten haben, können Sie entweder Ihre Hände in die Luft werfen und aufgeben, oder Sie können es erweitern oder verstopfen. (Dies ist sehr ähnlich wie das, was Noah darüber gesagt hat, es sei die Bibliothek, die streng/faul ist, nicht die Sprache.

Stellen Sie sich jetzt vor, dieselbe Funktion in strenger Scala zu schreiben:

def fix[A](f: A => A): A = f(fix(f))

val fact = fix[Int=>Int] { f => n =>
    if (n == 0) 1
    else n*f(n-1)
}

Sie erhalten natürlich einen Stapelüberlauf. Wenn Sie möchten, dass es funktioniert, müssen Sie das Argument f aufrufen, das nach Bedarf aufgerufen wird:

def fix[A](f: (=>A) => A): A = f(fix(f))

def fact1(f: =>Int=>Int) = (n: Int) =>
    if (n == 0) 1
    else n*f(n-1)

val fact = fix(fact1)
4
Owen

Dieser Ausschnitt zeigt den Unterschied zwischen fauler und nicht fauler Bewertung. Natürlich könnte diese Fibonacci-Funktion selbst optimiert werden und eine verzögerte Bewertung anstelle von Rekursion verwenden, aber dies würde das Beispiel stören. 

Nehmen wir an, wirK&OUML;NNENdie 20 ersten Zahlen für etwas verwenden müssen, bei einer nicht faulen Bewertung müssen alle 20 Zahlen vorab generiert werden, aber bei einer faulen Bewertung werden sie nur bei Bedarf generiert. So zahlen Sie bei Bedarf nur den Kalkulationspreis. 

Beispielausgabe

 Nicht faul Generation: 0,023373 
 Faule Generation: 0,000009 
 Nicht faul Ausgabe: 0.000921 
 Faule Ausgabe: 0,024205 
import time

def now(): return time.time()

def fibonacci(n): #Recursion for fibonacci (not-lazy)
 if n < 2:
  return n
 else:
  return fibonacci(n-1)+fibonacci(n-2)

before1 = now()
notlazy = [fibonacci(x) for x in range(20)]
after1 = now()
before2 = now()
lazy = (fibonacci(x) for x in range(20))
after2 = now()


before3 = now()
for i in notlazy:
  print i
after3 = now()

before4 = now()
for i in lazy:
  print i
after4 = now()

print "Not lazy generation: %f" % (after1-before1)
print "Lazy generation: %f" % (after2-before2)
print "Not lazy output: %f" % (after3-before3)
print "Lazy output: %f" % (after4-before4)
4
Vinko Vrsalovic

Ich weiß nicht, wie Sie derzeit über Dinge denken, aber ich halte es für nützlich, faule Evaluierung als ein Bibliotheksproblem und nicht als Sprachfunktion zu betrachten.

Ich meine, in strengen Sprachen kann ich eine faule Bewertung implementieren, indem ich einige Datenstrukturen aufbaue, und in faellen Sprachen (zumindest in Haskell) kann ich um strikte Aufforderung bitten, wann immer ich es will. Daher macht die Sprachauswahl Ihre Programme nicht wirklich faul oder nicht faul, sondern beeinflusst lediglich, welche Programme standardmäßig angezeigt werden.

Wenn Sie sich das einmal so vorstellen, denken Sie an all die Stellen, an denen Sie eine Datenstruktur schreiben, die Sie später verwenden können, um Daten zu generieren (ohne sie vorher zu viel anzusehen), und Sie werden viel zu faulenzen Auswertung.

3
Noah Lavine

Ohne faule Auswertung dürfen Sie so etwas nicht schreiben:

  if( obj != null  &&  obj.Value == correctValue )
  {
    // do smth
  }
2
peeles

Die nützlichste Nutzung von Lazy Evaluation, die ich verwendet habe, war eine Funktion, die eine Reihe von Unterfunktionen in einer bestimmten Reihenfolge aufrief. Wenn eine dieser Unterfunktionen fehlschlug (zurückgegeben wurde), musste die aufrufende Funktion sofort zurückkehren. So hätte ich es so machen können:

bool Function(void) {
  if (!SubFunction1())
    return false;
  if (!SubFunction2())
    return false;
  if (!SubFunction3())
    return false;

(etc)

  return true;
}

oder die elegantere Lösung:

bool Function(void) {
  if (!SubFunction1() || !SubFunction2() || !SubFunction3() || (etc) )
    return false;
  return true;
}

Sobald Sie mit der Verwendung beginnen, werden Sie immer öfter Gelegenheit haben, sie zu verwenden.

2
Marc Bernier

Faule Sprachen erlauben unter anderem multidimensional unendliche Datenstrukturen.

Während Schema, Python usw. eindimensionale unendliche Datenstrukturen mit Streams zulassen, können Sie nur entlang einer Dimension navigieren.

Faulheit ist nützlich für das gleiche Fringe-Problem , aber es lohnt sich, die in diesem Link genannte Coroutine-Verbindung zu beachten.

2
shapr

Bei Lazy Evaluation handelt es sich um eine schlechte Argumentation für die Gleichungen von Männern (von der erwartet werden könnte, dass sie im Idealfall Eigenschaften von Code aus den Eigenschaften der beteiligten Typen und Operationen ableitet). 

Beispiel, wo es gut funktioniert: sum . take 10 $ [1..10000000000]. Was uns nichts ausmacht, auf eine Summe von 10 Zahlen reduziert zu werden, anstatt nur eine direkte und einfache numerische Berechnung. Ohne die faule Bewertung würde dies natürlich eine gigantische Liste im Speicher erzeugen, nur um die ersten 10 Elemente zu verwenden. Dies wäre sicherlich sehr langsam und könnte zu einem Speicherfehler führen.

Beispiel, wo es nicht so gut ist, wie wir möchten: sum . take 1000000 . drop 500 $ cycle [1..20]. Damit werden tatsächlich die 1 000 000 Zahlen addiert, auch wenn sie sich in einer Schleife anstatt in einer Liste befinden. sollte auf nur eine direkte numerische Berechnung reduziert werden, mit wenigen Bedingungen und wenigen Formeln. Welches wäre wäre viel besser als die 1 000 000 Zahlen zusammenzufassen. Auch wenn es sich in einer Schleife befindet und nicht in einer Liste (d. H. Nach der Entwaldungsoptimierung).


Eine andere Sache ist, dass es möglich ist, in tail rekursion modulo cons style zu codieren, und es funktioniert einfach.

vgl. bezogene Antwort .

2
Will Ness

Wenn mit "fauler Bewertung" gemeint ist, wie in kombinierten Booleschen, wie in 

   if (ConditionA && ConditionB) ... 

die Antwort ist einfach: Je weniger CPU-Zyklen das Programm beansprucht, desto schneller wird es ausgeführt. Wenn ein Teil der Verarbeitungsanweisungen keinen Einfluss auf das Ergebnis des Programms hat, ist dies unnötig (und daher eine Verschwendung) Zeit) um sie trotzdem auszuführen ... 

wenn nicht, meinen Sie das, was ich als "faule Initialisierer" bezeichnet habe, wie in:

class Employee
{
    private int supervisorId;
    private Employee supervisor;

    public Employee(int employeeId)
    {
        // code to call database and fetch employee record, and 
        //  populate all private data fields, EXCEPT supervisor
    }
    public Employee Supervisor
    { 
       get 
          { 
              return supervisor?? (supervisor = new Employee(supervisorId)); 
          } 
    }
}

Nun, diese Technik erlaubt es dem Clientcode, die Klasse zu verwenden, um zu vermeiden, dass die Datenbank für den Supervisor-Datensatz aufgerufen werden muss, es sei denn, der Client, der das Employee-Objekt verwendet, benötigt Zugriff auf die Daten des Supervisors. Wenn Sie jedoch den Supervisor benötigen, löst der erste Aufruf der Supervisor-Eigenschaft den Aufruf der Datenbank aus, und die Daten werden abgerufen und verfügbar. 

1
Charles Bretana

Auszug aus Funktionen höherer Ordnung

Finden wir die größte Zahl unter 100.000, die durch 3829 teilbar ist. Dazu filtern wir nur eine Reihe von Möglichkeiten, in denen wir wissen Die Lösung liegt.

largestDivisible :: (Integral a) => a  
largestDivisible = head (filter p [100000,99999..])  
    where p x = x `mod` 3829 == 0 

Zuerst erstellen wir eine Liste aller Zahlen unter 100.000, absteigend . Dann filtern wir es nach unserem Prädikat und weil die Zahlen sortiert sind absteigend die größte Zahl, die unsere .__ erfüllt. Prädikat ist das erste Element der gefilterten Liste. Wir haben nicht einmal. benötigen eine endliche Liste für unser Startset. Das ist Faulheit in wieder Aktion. Weil wir nur den Kopf des gefilterten .__ verwenden. Es ist egal, ob die gefilterte Liste endlich oder unendlich ist . Die Bewertung stoppt, wenn die erste angemessene Lösung gefunden wurde.

0
onmyway133