Frage

Ich habe lange schon gewundert, warum faul Auswertung nützlich ist. Ich habe noch niemanden haben mir zu erklären, in einer Weise, die Sinn macht; es meist endet Einkochen auf „mir vertrauen“.

Hinweis: Damit meine ich nicht memoization.

War es hilfreich?

Lösung

Vor allem, weil es effizienter sein kann - Werte müssen nicht berechnet werden, wenn sie nicht benutzt werden. Zum Beispiel kann ich drei Werte in eine Funktion übergeben, sondern in Abhängigkeit von der Folge von bedingten Ausdrücken, nur eine Teilmenge tatsächlich verwendet werden. In einer Sprache wie C, alle drei Werte würden ohnehin berechnet werden; aber in Haskell, nur die erforderlichen Werte berechnet werden.

Es ermöglicht auch coole Sachen wie unendliche Listen. Ich kann nicht eine unendliche Liste in einer Sprache wie C, aber in Haskell, das ist kein Problem. Unendliche Listen sind ziemlich häufig in bestimmten Bereichen der Mathematik verwendet, so kann es sinnvoll sein, die Fähigkeit zu haben, um sie zu manipulieren.

Andere Tipps

Ein nützliches Beispiel für lazy evaluation ist die Verwendung von quickSort:

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

Wenn wir jetzt das Minimum der Liste finden möchten, können wir definieren

minimum ls = head (quickSort ls)

Welche sortiert zuerst die Liste und dann nimmt das erste Element der Liste. Da jedoch die lazy evaluation, nur der Kopf wird berechnet. Zum Beispiel wird, wenn wir das Minimum der Liste [2, 1, 3,] QuickSort nehmen zunächst alle Elemente herauszufiltern, die kleiner als zwei sind. Dann tut es QuickSort auf, dass (die Singleton-Liste zurückkehren [1]), die bereits genug. Wegen der lazy evaluation, der Rest wird nie sortiert, viel Rechenzeit zu sparen.

Das ist natürlich ein sehr einfaches Beispiel, aber Faulheit funktioniert auf die gleiche Art und Weise für Programme, die sehr groß sind.

Es ist jedoch ein Nachteil all dies: es schwieriger wird, die Laufzeitgeschwindigkeit und Speichernutzung Ihres Programms zu prognostizieren. Dies bedeutet nicht, dass faule Programme sind langsamer oder mehr Speicher nehmen, aber es ist gut zu wissen.

Ich finde lazy evaluation nützlich für eine Reihe von Dingen.

Zuerst werden alle vorhandenen faul Sprachen sind rein, weil es sehr schwer ist, über Nebenwirkungen in einem faulen Sprache zur Vernunft.

Reine Sprachen können Sie Grund, über Funktionsdefinitionen mit equational Argumentation.

foo x = x + 3

Leider gibt es in einer nicht-faul Einstellung, nicht mehr Aussagen zurück als in einer faulen Einstellung, so ist dies weniger nützlich in Sprachen wie ML. Aber in einer faulen Sprache können Sie sicher über die Gleichheit Vernunft.

Zum anderen sind viele Sachen wie die ‚Einschränkung‘ in ML nicht in faulen Sprachen wie Haskell benötigt. Dies führt zu einer großen Entrümpeln der Syntax. ML wie Sprachen benötigen Schlüsselwörter wie var oder Spaß zu verwenden. In Haskell kollabieren diese Dinge zu einer Vorstellung nach unten.

Drittens Faulheit kann Sie sehr funktionellen Code schreiben, der in Stücken verstanden werden kann. In Haskell ist es üblich, eine Funktion Körper wie 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 ‚top down‘, obwohl das Verständnis des Körpers einer Funktion arbeiten. ML-ähnliche Sprachen zwingen Sie let zu verwenden, die streng ausgewertet wird. Folglich wagst du nicht ‚Aufzug‘ die Klausel LET aus dem Hauptkörper der Funktion, denn wenn es teuer (oder hat Nebenwirkungen) Sie wollen es nicht immer ausgewertet werden. Haskell kann ‚abstoßen‘ die Details in die where-Klausel ausdrücklich, weil es der Inhalt dieser Klausel weiß, dass nur bei Bedarf ausgewertet werden.

In der Praxis neigen wir Wachen zu verwenden und kollabieren, dass weiter an:

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

Viertens Faulheit manchmal bietet viel eleganter Ausdruck bestimmter Algorithmen. Eine faule ‚schnelle Art‘ in Haskell ist ein Einzeiler und hat den Vorteil, dass, wenn Sie nur in den ersten paar Artikeln sehen Sie nur die Kosten auf die Kosten proportional zahlen nur die Elemente auszuwählen. Nichts hindert Sie daran, dies streng zu tun, aber Sie würden wahrscheinlich den Algorithmus jedes Mal neu codieren, um die gleiche asymptotische Leistung zu erzielen.

Fünfter, Faulheit können Sie neue Kontrollstrukturen in der Sprache definieren. Sie können keine neue ‚wenn .. dann .. sonst ..‘ schreiben wie in einer strengen Sprache aufzubauen. Wenn Sie versuchen, eine Funktion wie zu definieren:

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

in einer strengen Sprache dann würden beiden Zweige unabhängig vom Zustand Wert bewertet werden. Es wird noch schlimmer, wenn Sie Schleifen in Betracht ziehen. Alle strengen Lösungen erfordern die Sprache, die Sie mit irgendeiner Art von Angebot oder explizite Lambda-Konstruktion zu schaffen.

Schließlich wird in der gleichen Ader, einige der besten Mechanismen für die im Typsystem, wie Monaden mit Nebenwirkungen zu tun, kann es wirklich nur effektiv in einer faulen Einstellung ausgedrückt werden. Dies kann durch den Vergleich der Komplexität von F # 's Workflows zu Haskell Monaden erlebt werden. (Sie können eine Monade in einer strengen Sprache definieren, aber leider oft es eine Monade Gesetz oder zwei wegen des Mangels an Faulheit und Workflows durch den Vergleich nicht eine Tonne strengen Gepäck abholen.)

Es gibt einen Unterschied zwischen normaler Reihenfolge Auswertung eines lazy evaluation (wie in Haskell).

square x = x * x

Auswerten der folgenden Gleichung ...

square (square (square 2))

... mit eifriger Auswertung:

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

... mit normaler Reihenfolge Bewertung:

> (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 lazy evaluation:

> (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 ist, weil lazy evaluation an dem Syntaxbaum aussieht und funktioniert Baum-Transformationen ...

square (square (square 2))

           ||
           \/

           *
          / \
          \ /
    square (square 2)

           ||
           \/

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

           ||
           \/

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

... während der normalen Reihenfolge Auswertung nur, dass Texterweiterungen.

Das ist, warum wir, wenn faul Auswertung mit, leistungsfähigeren erhalten (Auswertung häufiger als andere Strategien beendet), während die Leistung zu eifrig Bewertung entspricht (zumindest in O-Notation).

Verzögerte Auswertung zu CPU RAM bezogen auf die gleiche Weise wie Garbage Collection verwendet. GC ermöglicht es Ihnen, behaupten, dass Sie unbegrenzte Menge an Speicher haben und damit so viele Objekte im Speicher anfordern, wie Sie benötigen. Runtime wird automatisch unbrauchbar Objekte zurückzufordern. LE ermöglicht es Ihnen, so zu tun, dass Sie unbegrenzte Rechenressourcen haben - Sie können so viele Berechnungen tun, wie Sie benötigen. Runtime wird nicht nur unnötig ausführen (für gegebenen Fall) Berechnungen.

Was ist der praktische Vorteil dieser „vorgibt“ Modelle? Es löst Entwickler (in gewissem Maße) von der Verwaltung der Ressourcen und entfernt einigen Textvorschlag Code aus Ihren Quellen. Aber wichtiger ist, dass Sie effizient Ihre Lösung in breiterem Spektrum von Kontexten wiederverwenden können.

Stellen Sie sich vor, dass Sie eine Liste von Zahlen S und eine Anzahl N. Sie müssen die Nähe finden N Anzahl M von der Liste S. auf Nummer können Sie zwei Kontexte: Einzel N und eine Liste L von Ns (ei für jede N in L sehen Sie den nächsten M in S nach oben). Wenn Sie faul Auswertung verwenden, können Sie S sortieren und binäre Suche beziehen sich auf die am nächsten M N. Für eine gute faul Sortierung es O erfordert zu finden (Größe (S)) Schritte für die einzelnen N und O (ln (Größe (S)) * (Größe (S) + Größe (L))) Schritte für gleichmäßig verteilt L. Wenn Sie nicht faul Auswertung haben die optimale Effizienz Sie Algorithmus für jeden Kontext implementieren zu erreichen.

Wenn Sie Simon Peyton Jones, lazy evaluation glauben, ist nicht wichtig per se , sondern nur als ‚Büßerhemd‘, die die Designer gezwungen, die Sprache rein zu halten. Ich finde mich zu dieser Sicht sympathisch.

Richard Vogel, John Hughes, und in geringerem erweitern Ralf Hinze sind in der Lage erstaunliche Dinge mit lazy evaluation zu tun. ihre Arbeit lesen, werden Ihnen helfen, es zu schätzen wissen. Um eine gute Ausgangspunkte sind Vogel großartigen Sudoku Solver und Hughes Papier auf Warum Functional Programming Matters .

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

  • A move-Erzeugungsfunktion, die einen aktuellen Board nimmt und erzeugt eine Liste der neuen Platinen jeweils mit einem Zug aufgebracht.
  • Dann gibt es eine „move Baum“ Funktion, die die Bewegung Erzeugungsfunktion gilt alle möglichen Vorstandspositionen abzuleiten, die von diesem folgen konnte.
  • Es gibt eine Minimax-Funktion, die den Baum geht (oder möglicherweise nur einen Teil davon), um den besten nächsten Zug zu finden.
  • Es ist eine Brett-Bewertungsfunktion, die, wenn einer der Spieler bestimmt, hat gewonnen.

Dies schafft eine schöne klare Trennung von Bedenken. Insbesondere sind die Move-Generation-Funktion und die Board-Auswerte-Funktionen die einzigen, die die Regeln des Spiels verstehen müssen: der Umzug Baum und Minimax-Funktionen sind vollständig wiederverwendbar

.

Jetzt kann versuchen Schach Umsetzung anstelle von Tic-Tac-Toe. In einer „eifrig“ (das heißt konventionell) Sprache wird dies nicht funktionieren, weil der Umzug Baum nicht im Speicher passen. So, jetzt die Board-Auswerte und bewegen müssen Generation Funktionen gemischt werden in mit dem Umzug Baum und Minimax-Logik, da die Minimax-Logik verwendet werden muss, um zu entscheiden, welche zu erzeugen bewegt. Unsere schöne saubere modulare Struktur verschwindet.

Jedoch in einem faulen Sprache sind die Elemente des Umzugs Baum nur als Reaktion auf Forderungen aus der Minimax-Funktion generiert: die gesamte Bewegung Baum muss nicht erzeugt werden, bevor wir auf dem oberen Element Minimax locker lassen. So ist unsere klare modulare Struktur funktioniert immer noch in einem realen Spiel.

Hier sind zwei weitere Punkte, die ich glaube nicht, noch in der Diskussion gebracht worden war.

  1. Laziness ist ein Synchronisationsmechanismus in einer Umgebung mit gemeinsamer Zugriff. Es ist eine leichte und einfache Art und Weise einen Verweis auf einige Berechnung zu erstellen, und ihre Ergebnisse unter vielen Threads teilen. Wenn mehrere Threads versuchen, eine nicht ausgewertete Wert zuzugreifen, nur einer von ihnen wird es ausführen, und die anderen werden entsprechend sperren, um den Wert empfangen, sobald sie verfügbar sind.

  2. Laziness ist von grundlegender Bedeutung Datenstrukturen in einer reinen Einstellung zu amortisieren. Dies wird durch Okasaki in rein funktionale Datenstrukturen im Detail beschrieben, aber die Grundidee ist, dass lazy evaluation eine kontrollierte Form der Mutation ist entscheidend für die es uns ermöglicht, effizient bestimmte Arten von Datenstrukturen zu implementieren. Während wir oft Faulheit sprechen die uns zwingen, die Reinheit hairshirt zu tragen, gilt die andere Art und Weise zu: sie ein Paar von synergistischen Sprachfunktionen sind

  3. .

Wenn Sie Ihren Computer einschalten und Windows enthält sich jedes einzelne Verzeichnis auf der Festplatte in Windows Explorer öffnen und verzichtet auf jedes einzelne Programm auf Ihrem Computer installiert starten, bis Sie angeben, dass ein bestimmtes Verzeichnis benötigt wird oder ein bestimmtes Programm ist erforderlich, das heißt "faul" Auswertung.

„faule“ Auswertung Operationen durchführen, wann und wie sie gebraucht werden. Es ist nützlich, wenn es sich um ein Merkmal einer Programmiersprache oder Bibliothek ist, weil es in der Regel härter ist faul Auswertung auf eigene Faust zu implementieren, als einfach alles vorne vorauszuberechnen.

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

  2. Es läßt Sie Konstrukte zur Ablaufsteuerung in normalen User-Level-Code zu definieren, anstatt sie in die Sprache hartcodiert werden. (Zum Beispiel Java hat for Schleifen; Haskell eine for Funktion hat Java hat Handhabung Ausnahme;.. Haskell verschiedene Arten von Ausnahmen Monade hat C # hat goto; Haskell hat die Fortsetzung Monade ...)

  3. Damit können Sie den Algorithmus für Erzeugen Daten aus dem Algorithmus für die Entscheidung wie viel Daten zu erzeugen entkoppeln. Sie können eine Funktion schreiben, die eine fiktiv-unendliche Liste der Ergebnisse erzeugt, und eine andere Funktion, die so viel von dieser Liste verarbeitet, wie es entscheidet er braucht. Mehr zu dem Punkt, können Sie fünf Generator-Funktionen und fünf Verbraucher Funktionen, und Sie können eine beliebige Kombination effizient produzieren - statt der manuellen Kodierung 5 x 5 = 25 Funktionen, die verbinden beide Aktionen auf einmal. (!) Wir alle wissen, Entkoppelung eine gute Sache ist.

  4. Es ist mehr oder weniger Kräfte Sie eine rein funktionale Sprache zu entwerfen. Es ist immer verlockend, Abkürzungen zu nehmen, aber in einer faulen Sprache, die geringste Verunreinigung macht den Code wild unberechenbar, die gegen nehmen Abkürzungen stark streitet.

Bedenken Sie:

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

Das Verfahren doSomething () ausgeführt wird, nur dann, wenn conditionOne true und conditionTwo ist wahr. In dem Fall, in dem conditionOne falsch ist, warum brauchen Sie das Ergebnis der conditionTwo zu berechnen? Die Bewertung der conditionTwo wird eine Verschwendung von Zeit in diesem Fall sein, vor allem, wenn Ihr Zustand das Ergebnis einer Methode Prozess ist.

Das ist ein Beispiel für das lazy evaluation Interesse ...

Ein großer Vorteil der Faulheit ist die Fähigkeit, unveränderlich Datenstrukturen mit vernünftigen amortisierten Grenzen zu schreiben. Ein einfaches Beispiel ist ein unveränderlicher Stapel (unter Verwendung von 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 Anfügen zwei Stapel x und y nimmt O (Länge x) Zeit in besten, am schlechtesten, und durchschnittlichen Fällen. Anfügen zwei Stapel ist ein monolithischer Betrieb, es berührt alle Knoten in Stapeln x.

Wir können die Datenstruktur als faul Stapel neu schreiben:

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 funktioniert durch die Auswertung von Code im Konstruktor suspendieren. Sobald .Force() ausgewertet, wird der Rückgabewert zwischengespeichert und wiederverwendet zu allen anderen .Force().

Mit der faulen Version sind Hängt eine O (1) Betrieb: es gibt 1 Knoten und unterbricht den tatsächlichen Wiederaufbau der Liste. Wenn Sie den Kopf dieser Liste zu bekommen, wird es den Inhalt des Knotens zu bewerten, sie zurückbringen, den Kopf zu zwingen und eine Suspension mit den übrigen Elementen erstellen, so die Liste, um den Kopf zu nehmen ist eine O (1) -Operation.

Also, unsere faul Liste ist in einem ständigen Zustand der Wiederaufbau, zahlen Sie nicht die Kosten für den Wiederaufbau dieser Liste, bis Sie alle Elemente durchqueren. Verwendung Trägheit unterstützt diese Liste O (1) consing und Anfügen. Interessanterweise, da wir ihre zugegriffen nicht Knoten auswerten, bis seine hundert möglich, eine Liste mit potentiell unendlichen Elementen zu konstruieren.

Die Datenstruktur, die oben nicht Knoten erfordert an jedem Traversal neu berechnet werden, so dass sie deutlich von Vanille IEnumerables in .NET.

Verzögerte Auswertung ist am nützlichsten, mit Datenstrukturen. Sie können ein Array oder einen Vektor definieren induktiv nur bestimmte Punkte in der Struktur spezifizieren und alle anderen in Bezug auf das gesamte Array ausdrückt. Auf diese Weise können Sie Datenstrukturen sehr prägnant und mit hohen Laufzeitleistung zu erzeugen.

Um dies zu sehen in Aktion können Sie einen Blick auf meine neuronales Netzwerk Bibliothek namens Instinkt . Es macht intensiven Gebrauch von lazy evaluation für Eleganz und hohe Leistung. Zum Beispiel werde ich völlig von der traditionell zwingend notwendig Aktivierung Berechnung befreien. Ein einfacher faul Ausdruck tut alles für mich.

Dies ist beispielsweise in der Aktivierungsfunktion und auch im Backpropagation Lernalgorithmus (I nur zwei Links veröffentlichen können, so dass Sie die learnPat Funktion im AI.Instinct.Train.Delta Modul selbst nachschlagen werden müssen). Traditionell erfordern beide viel komplizierter iterative Algorithmen.

Dieser Code-Schnipsel zeigt den Unterschied zwischen faul und nicht faul Auswertung. Natürlich könnte diese Fibonacci-Funktion selbst optimiert und faul Auswertung statt Rekursion verwenden, sondern dass das Beispiel verderben würde.

Nehmen wir an, wir MAY haben die 20 ersten Zahlen für etwas zu verwenden, mit nicht faul Auswertung alle 20 Zahlen haben im Voraus erzeugt werden, aber mit lazy evaluation sie erzeugt werden, werden je nach Bedarf nur. So zahlen Sie nur die Berechnung Preis bei Bedarf.

Eine Beispielausgabe

Not lazy generation: 0.023373
Lazy generation: 0.000009
Not lazy output: 0.000921
Lazy output: 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)

Andere Leute schon gab alle großen Gründe, aber ich denke, eine nützliche Übung um zu verstehen, warum Faulheit Angelegenheiten ist zu versuchen, und schreiben Sie eine Festpunkt Funktion in einer strengen Sprache.

In Haskell, eine Festpunktfunktion ist super einfach:

fix f = f (fix f)

Dies erweitert zu

f (f (f ....

, sondern weil Haskell faul ist, dass unendliche Kette von Berechnung ist kein Problem; die Auswertung 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, dass es wichtig ist nicht, dass fix faul sein, aber das f faul sein. Wenn Sie bereits eine strenge f gegeben haben, können Sie entweder Ihre Hände in die Luft werfen und aufgeben, oder eta es und Unordnung Zeug erweitern. (Dies ist ein viel wie, was Noah sagt darüber zu sein, die Bibliothek das ist streng / faul, nicht die Sprache).

Nun stellen die gleiche Funktion in strikter Scala 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 natürlich einen Stapelüberlauf bekommen. Wenn Sie es arbeiten möchten, müssen Sie das Argument f Call-by-need machen:

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)

Ich weiß nicht, wie man zur Zeit der Dinge denken, aber ich finde es sinnvoll, von lazy evaluation als Bibliothek Problem zu denken, anstatt eine Sprache-Funktion.

Ich meine, dass in strengen Sprachen, ich durch den Bau von ein paar Datenstrukturen lazy evaluation implementieren können, und in faulen Sprachen (mindestens Haskell), kann ich für Strikt fragen, wann ich es will. Daher ist die Auswahl der Sprache nicht wirklich Ihre Programme faul oder nicht faul, sondern einfach beeinflusst, die Sie standardmäßig erhalten.

Wenn Sie es so denken, dann denken Sie an allen Orten, wo man eine Datenstruktur zu schreiben, die Sie später verwenden können, um Daten zu erzeugen (ohne zu viel es zu betrachten, bevor dann), und Sie werden eine Menge sehen verwendet für faule Auswertung.

Die nützlichste Ausbeutung von lazy evaluation, die ich verwendet habe, war eine Funktion, die eine Reihe von Teilfunktionen in einer bestimmten Reihenfolge aufgerufen. Wenn eine dieser Unterfunktionen fehlgeschlagen (zurück false), benötigt die anrufende Funktion sofort zurück. So konnte ich habe es so gemacht:

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;
}

Wenn Sie es benutzen, werden Sie Möglichkeiten sehen, es zu benutzen immer häufiger.

Ohne lazy evaluation Sie werden nicht erlaubt sein, so etwas zu schreiben:

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

Unter anderem faul Sprachen ermöglichen mehrdimensionale unendliche Datenstrukturen.

Während Schema, Python, etc. erlaubt eindimensionale unendliche Datenstrukturen mit Bächen, können Sie nur Traverse entlang einer Dimension.

Laziness ist nützlich für das gleiche Rand Problem , aber es ist erwähnenswert, die Koroutinen Verbindung erwähnt in dieser Verbindung.

Verzögerte Auswertung ist die arme Mann equational Argumentation (die im Idealfall zu erwarten, Eigenschaften von Code von Eigenschaften von Typen und Operationen beteiligt werden herzuleiten).

Beispiel, wo es funktioniert recht gut: sum . take 10 $ [1..10000000000]. Welche wir nichts dagegen haben, auf eine Summe von 10 Zahlen reduziert zu werden, statt nur eine direkte und einfache numerische Berechnung. Ohne die lazy evaluation natürlich würde dies eine gigantische Liste im Speicher erstellen gerade seine ersten 10 Elemente zu verwenden. Es wäre sicherlich sehr langsam sein, und könnte einen Out-of-Memory-Fehler verursachen.

Beispiel, wo es ist nicht so groß, wie wir möchten: sum . take 1000000 . drop 500 $ cycle [1..20]. Welche wird die 1 000 000 Zahlen tatsächlich summiert, auch wenn in einer Schleife statt in einer Liste; es immer noch sollte , um nur eine direkte numerische Berechnung reduziert werden, mit wenigen conditionals und einige Formeln. Welche würde viel besser sein dann die 1 000 000 Zahlen zusammen. Auch wenn in einer Schleife, und nicht in einer Liste (das heißt nach der Abholzung-Optimierung).


Eine andere Sache ist, macht es möglich, in Endrekursion Modulo cons rel="nofollow zu codieren Stil, und es nur funktioniert .

cf. bezogene Antwort .

Wenn von "lazy evaluation" Sie bedeuten, wie in combound booleans, wie in

   if (ConditionA && ConditionB) ... 

dann ist die Antwort einfach, dass die weniger CPU-Zyklen das Programm verbraucht, desto schneller läuft es ... und wenn ein Stück Verarbeitungsanweisungen wird keine Auswirkungen auf denen das Ergebnis des Programms hat, dann ist es unnötig, (und daher eine Verschwendung von Zeit) durchzuführen, sie trotzdem ...

Wenn OTOH, meinen Sie, was ich als "faul initializers" bekannt ist, 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 Client-Code ermöglicht die Klasse mit der Notwendigkeit zu vermeiden, die Datenbank für den Supervisor-Datensatz außer zu rufen, wenn der Kunde die Employee-Objekt erfordert den Zugriff auf die Daten des Supervisor ... das macht den Prozess ein instanziieren Mitarbeiter schneller, und doch, wenn Sie die Betreuer benötigen, um den ersten Anruf an den Supervisor-Eigenschaft wird die Datenbank aufrufen und die Daten auslösen wird abgerufen und zur Verfügung stehen ...

Auszug aus Funktionen höherer Ordnung

  

Lassen Sie sich die größte Zahl unter 100.000 finden, die von 3829 teilbar ist.   Um dies zu erreichen, werden wir filtern, dass 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 
  

Wir zunächst eine Liste aller Zahlen niedriger als 100.000 machen, absteigend.   Dann haben wir es durch unser Prädikat filtern und weil die Zahlen sortiert   in einer absteigenden Art und Weise, die die größte Zahl erfüllt unsere   Prädikat ist das erste Element der Liste gefiltert. Wir haben nicht einmal   benötigt eine endliche Liste für unseren Ausgangssatz zu verwenden. Das ist Faulheit in   Aktion wieder. Da wir nur mit dem Kopf des gefilterten Ende   Liste, spielt es keine Rolle, ob die gefilterte Liste endlich oder unendlich ist.   Die Auswertung wird beendet, wenn die erste adäquate Lösung gefunden wird.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top