web-dev-qa-db-fra.com

Pourquoi l'évaluation paresseuse est-elle utile?

Je me demande depuis longtemps pourquoi une évaluation paresseuse est utile. Je n'ai encore personne à m'expliquer d'une manière qui a du sens; la plupart du temps, cela finit par se résumer à "me faire confiance".

Remarque: je ne veux pas dire mémoization. 

110
Joel McCracken

Principalement parce que cela peut être plus efficace - les valeurs n'ont pas besoin d'être calculées si elles ne vont pas être utilisées. Par exemple, je peux passer trois valeurs dans une fonction, mais selon la séquence d'expressions conditionnelles, seul un sous-ensemble peut être utilisé. Dans un langage comme C, les trois valeurs seraient de toute façon calculées; mais en Haskell, seules les valeurs nécessaires sont calculées.

Il permet également des choses intéressantes comme des listes infinies. Je ne peux pas avoir une liste infinie dans un langage comme C, mais en Haskell, ce n'est pas un problème. Les listes infinies étant utilisées assez souvent dans certains domaines des mathématiques, il peut être utile de pouvoir les manipuler.

90
mipadi

Un exemple utile d'évaluation paresseuse est l'utilisation de quickSort:

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

Si nous voulons maintenant trouver le minimum de la liste, nous pouvons définir

minimum ls = head (quickSort ls)

Le premier trie la liste puis prend le premier élément de la liste. Cependant, à cause d'une évaluation paresseuse, seule la tête est calculée. Par exemple, si nous prenons le minimum de la liste, [2, 1, 3,] quickSort filtrera d’abord tous les éléments dont la taille est inférieure à deux. Ensuite, il effectue un tri rapide (renvoyant la liste de singleton [1]), ce qui est déjà suffisant. En raison d'une évaluation paresseuse, le reste n'est jamais trié, ce qui permet de gagner beaucoup de temps de calcul.

Ceci est bien sûr un exemple très simple, mais la paresse fonctionne de la même manière pour les programmes très volumineux.

Il y a cependant un inconvénient à tout cela: il devient plus difficile de prédire la vitesse d'exécution et l'utilisation de la mémoire de votre programme. Cela ne signifie pas que les programmes paresseux sont plus lents ou prennent plus de mémoire, mais il est bon de savoir.

69
Chris Eidhof

Je trouve l'évaluation paresseuse utile pour un certain nombre de choses. 

Premièrement, tous les langages paresseux existants sont purs, car il est très difficile de raisonner sur les effets secondaires dans un langage paresseux.

Les langages purs vous permettent de raisonner sur les définitions de fonctions en utilisant un raisonnement équationnel.

foo x = x + 3

Malheureusement, dans un environnement non paresseux, plus d'instructions échouent que dans un environnement paresseux. Cela est donc moins utile dans des langages tels que ML. Mais dans un langage paresseux, vous pouvez raisonner en toute sécurité sur l'égalité.

Deuxièmement, beaucoup de choses comme la «restriction de valeur» dans ML ne sont pas nécessaires dans des langages paresseux comme Haskell. Cela conduit à un grand désencombrement de la syntaxe. ML comme les langues doivent utiliser des mots clés comme var ou fun. En Haskell, ces choses se résument en une seule notion.

Troisièmement, la paresse vous permet d’écrire un code très fonctionnel qui peut être compris par morceaux. En Haskell, il est courant d'écrire un corps de fonction comme:

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 = ...

Cela vous permet de travailler de haut en bas si vous comprenez le corps d'une fonction. Les langages de type ML vous obligent à utiliser un let qui est évalué strictement. Par conséquent, vous n'osez pas «lever» la clause let au corps principal de la fonction, car si elle est coûteuse (ou a des effets secondaires), vous ne voulez pas qu'elle soit toujours évaluée. Haskell peut «pousser» les détails de la clause where explicitement, car il sait que le contenu de cette clause ne sera évalué qu'en fonction des besoins.

En pratique, nous avons tendance à utiliser des protections et à nous effondrer pour:

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

Quatrièmement, la paresse offre parfois une expression beaucoup plus élégante de certains algorithmes. Un «tri rapide» paresseux dans Haskell est un one-line et présente l'avantage que si vous ne regardez que les premiers éléments, vous ne payez que des coûts proportionnels au coût de sélection de ces éléments. Rien ne vous empêche de le faire strictement, mais vous devrez probablement recoder l'algorithme à chaque fois pour obtenir les mêmes performances asymptotiques.

Cinquièmement, la paresse vous permet de définir de nouvelles structures de contrôle dans le langage. Vous ne pouvez pas écrire un nouveau "si ... alors ... sinon ..." comme construit dans un langage strict. Si vous essayez de définir une fonction comme:

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

dans un langage strict, les deux branches seraient évaluées quelle que soit la valeur de la condition. Il y a pire quand on considère les boucles. Toutes les solutions strictes exigent que le langage vous fournisse une sorte de citation ou une construction explicite de lambda.

Enfin, dans le même ordre d'idées, certains des meilleurs mécanismes pour traiter les effets secondaires dans le système typographique, tels que les monades, ne peuvent vraiment être exprimés efficacement que dans un environnement paresseux. Ceci peut être constaté en comparant la complexité des flux de travail de F # aux monades Haskell. (Vous pouvez définir une monade dans un langage strict, mais malheureusement, vous échouerez souvent dans une loi sur la monade en raison d'un manque de paresse et les flux de travail comparés ramasseront une tonne de bagages strict.)

62
Edward KMETT

Il y a une différence entre une évaluation d'ordre normal et une évaluation paresseuse (comme dans Haskell).

square x = x * x

Evaluer l'expression suivante ...

square (square (square 2))

... avec une évaluation avide:

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

... avec une évaluation de commande normale:

> (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

... avec évaluation paresseuse:

> (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

En effet, une évaluation paresseuse examine l'arbre de syntaxe et effectue des transformations d'arbre ...

square (square (square 2))

           ||
           \/

           *
          / \
          \ /
    square (square 2)

           ||
           \/

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

           ||
           \/

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

... alors que l'évaluation d'ordre normal ne fait que des extensions de texte.

C'est pourquoi, lorsque nous utilisons une évaluation paresseuse, nous obtenons plus de puissance (l'évaluation se termine plus souvent que d'autres stratégies) alors que la performance est équivalente à une évaluation avide (au moins en notation O).

28
Thomas Danecker

Évaluation paresseuse liée au processeur de la même manière que la récupération de place liée à la RAM. GC vous permet de prétendre que vous avez une quantité de mémoire illimitée et de demander ainsi autant d'objets en mémoire que nécessaire. L'exécution récupérera automatiquement les objets inutilisables. LE vous permet de prétendre que vous avez des ressources de calcul illimitées - vous pouvez faire autant de calculs que nécessaire. Runtime n'exécutera tout simplement pas de calculs inutiles (pour des cas donnés).

Quel est l'avantage pratique de ces modèles "simulateurs"? Il libère le développeur (dans une certaine mesure) de la gestion des ressources et supprime du code standard de vos sources. Mais le plus important est que vous puissiez réutiliser efficacement votre solution dans un ensemble de contextes plus large.

Imaginez que vous avez une liste de nombres S et un nombre N. Vous devez trouver le plus proche du nombre N nombre M de la liste S. Vous pouvez avoir deux contextes: un seul N et une liste L de Ns (ei pour chaque N dans L vous recherchez le M le plus proche en S). Si vous utilisez une évaluation paresseuse, vous pouvez trier S et appliquer la recherche binaire pour trouver le plus proche de M à N. Pour effectuer un bon tri paresseux, il faudra O(size(S)) pour N et O (ln (taille ( S)) * (taille (S) + taille (L))) étapes pour une distribution égale de L. Si vous ne disposez pas d'une évaluation paresseuse pour obtenir l'efficacité optimale, vous devez implémenter un algorithme pour chaque contexte.

25
Alexey

Si vous en croyez Simon Peyton Jones, l’évaluation paresseuse n’est pas importante en tant que telle mais uniquement en tant que «chemise de cheveux» qui a obligé les concepteurs à garder la langue pure. Je me trouve sympathique à ce point de vue.

Richard Bird, John Hughes et, dans une moindre mesure, Ralf Hinze sont capables de faire des choses étonnantes avec une évaluation paresseuse. La lecture de leur travail vous aidera à l'apprécier. Le solveur le magnifique Sudoku de Bird et le document de Hughes sur Pourquoi la programmation fonctionnelle est importante .

25
Norman Ramsey

Considérons un programme de tic-tac-toe. Cela a quatre fonctions:

  • Une fonction de génération de mouvements qui prend un tableau actuel et génère une liste de nouveaux tableaux avec chacun un mouvement appliqué.
  • Il existe ensuite une fonction "arbre de déplacement" qui applique la fonction de génération de déplacement pour dériver toutes les positions possibles du conseil pouvant découler de celle-ci. 
  • Il existe une fonction minimax qui parcourt l’arbre (ou éventuellement seulement une partie de celui-ci) pour trouver le meilleur coup suivant.
  • Il existe une fonction d'évaluation du conseil qui détermine si l'un des joueurs a gagné.

Cela crée une séparation claire et nette des préoccupations. En particulier, la fonction de génération de mouvements et les fonctions d'évaluation du tableau sont les seules à avoir besoin de comprendre les règles du jeu: les fonctions Déplacement et Minimax sont entièrement réutilisables.

Essayons maintenant de mettre en place des échecs au lieu de tic-tac-toe. Dans un langage "impatient" (c'est-à-dire conventionnel), cela ne fonctionnera pas car l'arbre de déplacement ne tiendra pas dans la mémoire. Alors maintenant, les fonctions d’évaluation des cartes et de génération de mouvements doivent être combinées avec l’arbre de déplacement et la logique minimax, car la logique minimax doit être utilisée pour décider des déplacements à générer. Notre belle structure modulaire épurée disparaît.

Cependant, dans un langage paresseux, les éléments de l’arborescence des déplacements ne sont générés que pour répondre aux demandes de la fonction minimax: il n’est pas nécessaire de générer l’arborescence entière avant de laisser l’élément minimax décoller. Donc, notre structure modulaire épurée fonctionne toujours dans un vrai jeu.

13
Paul Johnson

Voici deux autres points qui, à mon avis, n’ont pas encore été abordés dans la discussion.

  1. La paresse est un mécanisme de synchronisation dans un environnement concurrent. C'est un moyen simple et léger de créer une référence à certains calculs et de partager ses résultats entre plusieurs threads. Si plusieurs threads tentent d'accéder à une valeur non évaluée, un seul d'entre eux l'exécutera et les autres bloqueront en conséquence, recevant la valeur une fois qu'elle sera disponible.

  2. La paresse est fondamentale pour amortir les structures de données dans un contexte pur. Ceci est décrit par Okasaki dans Structures de données purement fonctionnelles en détail, mais l’idée de base est que l’évaluation paresseuse est une forme contrôlée de mutation essentielle à la mise en œuvre efficace de certains types de structures de données. Bien que nous parlions souvent de paresse nous obligeant à porter le t-shirt pureté, l’inverse s’applique également: il s’agit d’une paire de traits de langage synergiques.

12
Edward Z. Yang

Lorsque vous allumez votre ordinateur et que Windows s'abstient d'ouvrir tous les répertoires de votre disque dur dans l'Explorateur Windows et de lancer chaque programme installé sur votre ordinateur, jusqu'à ce que vous indiquiez qu'un répertoire ou des programmes sont nécessaires, est l'évaluation "paresseuse".

Une évaluation "paresseuse" consiste à effectuer des opérations quand et quand elles sont nécessaires. C'est utile quand il s'agit d'une fonctionnalité d'un langage de programmation ou d'une bibliothèque, car il est généralement plus difficile de mettre en œuvre une évaluation paresseuse que de simplement tout pré-calculer à l'avance.

9
yfeldblum
  1. Cela peut augmenter l'efficacité. C’est l’aspect évident, mais ce n’est pas réellement le plus important. (Notez également que la paresse peut tuer l'efficacité de . Ce fait n'est pas évident. Cependant, en stockant de nombreux résultats temporaires au lieu de les calculer immédiatement, vous pouvez utiliser une quantité énorme de RAM.)

  2. Il vous permet de définir des constructions de contrôle de flux dans un code de niveau utilisateur normal, plutôt que de les coder en dur dans le langage. (Par exemple, Java a for boucles; Haskell a une fonction for. Java a la gestion des exceptions; Haskell a différents types d'exception monad. C # a goto; Haskell a la suite monade ...)

  3. Il vous permet de découpler l'algorithme pour générer des données à partir de l'algorithme permettant de décider combien données à générer. Vous pouvez écrire une fonction qui génère une liste de résultats théoriquement infinie, et une autre fonction qui traite autant de cette liste que nécessaire. Plus précisément, vous pouvez avoir cinq fonctions de générateur et cinq consommateurs fonctions, et vous pouvez efficacement produire n'importe quelle combinaison - au lieu de coder manuellement 5 x 5 = 25 fonctions combinant les deux actions en même temps. (!) Nous savons tous que le découplage est une bonne chose.

  4. Cela vous oblige plus ou moins à concevoir un langage fonctionnel pur . Il est toujours tentant de prendre des raccourcis, mais dans un langage paresseux, la moindre impureté rend votre code énormément imprévisible, ce qui empêche fortement de prendre des raccourcis.

8

Un des grands avantages de la paresse est la possibilité d'écrire des structures de données immuables avec des limites amorties raisonnables. Un exemple simple est une pile immuable (utilisant 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)

Le code est raisonnable, mais ajouter deux piles x et y prend O (longueur de x) fois dans les cas les meilleurs, les pires et les cas moyens. L'ajout de deux piles est une opération monolithique, il touche tous les nœuds de la pile x.

Nous pouvons réécrire la structure de données sous forme de pile paresseuse:

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 fonctionne en suspendant l'évaluation du code dans son constructeur. Une fois évaluée avec .Force(), la valeur de retour est mise en cache et réutilisée à chaque .Force() suivante.

Avec la version lazy, les ajouts constituent une opération O(1): il renvoie 1 nœud et suspend la reconstruction réelle de la liste. Lorsque vous obtenez la tête de cette liste, il évalue le contenu du noeud, le forçant à retourner la tête et en créant une suspension avec les éléments restants. Prendre la tête de la liste est donc une opération O(1). .

Donc, notre liste paresseuse est dans un état de reconstruction constant, vous ne payez pas le coût de la reconstruction de cette liste tant que vous n'avez pas parcouru tous ses éléments. En utilisant la paresse, cette liste prend en charge O(1) et l’ajout. Fait intéressant, comme nous n’évaluons pas les nœuds tant qu’ils n’ont pas été consultés, il est tout à fait possible de construire une liste avec des éléments potentiellement infinis.

La structure de données ci-dessus n'exige pas que les nœuds soient recalculés à chaque parcours, ils sont donc nettement différents de Vanilla IEnumerables dans .NET.

6
Juliet

Considère ceci:

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

La méthode doSomething () sera exécutée uniquement si conditionOne est vraie et conditionTwo est vraie . Dans le cas où conditionOne est fausse, pourquoi devez-vous calculer le résultat de la conditionDeux? Dans ce cas, l’évaluation de conditionTwo sera une perte de temps, surtout si votre condition résulte d’un processus quelconque.

C'est un exemple de l'intérêt de l'évaluation paresseuse ...

6
Romain Linsolas

L'évaluation paresseuse est plus utile avec les structures de données. Vous pouvez définir un tableau ou un vecteur inductif en spécifiant uniquement certains points de la structure et en exprimant tous les autres en termes de tableau entier. Cela vous permet de générer des structures de données de manière très concise et avec des performances d'exécution élevées.

Pour voir cela en action, vous pouvez consulter ma bibliothèque de réseaux de neurones appelée instinct . Il utilise beaucoup l'évaluation paresseuse pour l'élégance et la haute performance. Par exemple, je me débarrasse totalement du calcul d'activation traditionnellement impératif. Une simple expression paresseuse fait tout pour moi.

Ceci est utilisé par exemple dans la fonction activation ainsi que dans l'algorithme d'apprentissage de la rétropropagation (je ne peux poster que deux liens; vous devrez donc rechercher vous-même la fonction learnPat dans le module AI.Instinct.Train.Delta). Traditionnellement, les deux nécessitent des algorithmes itératifs beaucoup plus compliqués.

5
ertes

D'autres personnes ont déjà donné toutes les grandes raisons, mais je pense qu'un exercice utile pour aider à comprendre pourquoi la paresse est importante est d'essayer d'écrire un point fixe dans un langage strict.

En Haskell, une fonction de point fixe est très facile:

fix f = f (fix f)

cela se développe à

f (f (f ....

mais comme Haskell est paresseux, cette chaîne de calcul infinie n’est pas un problème; l'évaluation se fait "de l'extérieur vers l'intérieur" et tout fonctionne à merveille:

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

Fait important, il importe peu que fix soit paresseux, mais que (f _ soit paresseux. Une fois que vous avez déjà reçu une variable f stricte, vous pouvez jeter les mains en l'air et abandonner, ou bien l'étendre et l'encombrer. (Cela ressemble beaucoup à ce que Noah disait à propos d’être une bibliothèque stricte/paresseuse, pas la langue).

Maintenant, imaginez écrire la même fonction en stricte Scala:

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

Vous avez bien sûr un débordement de pile. Si vous voulez que cela fonctionne, vous devez définir l'argument f comme suit:

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

Cet extrait montre la différence entre une évaluation paresseuse et non paresseuse. Bien sûr, cette fonction fibonacci pourrait elle-même être optimisée et utiliser une évaluation paresseuse au lieu d'une récursion, mais cela gâcherait l'exemple. 

Supposons que nousPUISSIONSutiliser les 20 premiers nombres pour quelque chose. Avec une évaluation non paresseuse, tous les 20 numéros doivent être générés à l’avance, mais, avec une évaluation paresseuse, ils ne seront générés qu’au besoin. Ainsi, vous ne paierez que le prix du calcul en cas de besoin. 

Échantillon de sortie

 Génération non paresseuse: 0.023373 
 Génération paresseuse: 0.000009 
 Sortie non paresseuse: 0.000921 
 Sortie paresseuse: 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

Je ne sais pas comment vous pensez actuellement, mais j'estime utile de considérer l'évaluation paresseuse comme un problème de bibliothèque plutôt que comme un élément de langage.

Je veux dire que dans des langages stricts, je peux mettre en œuvre une évaluation paresseuse en construisant quelques structures de données, et dans des langages paresseux (au moins en Haskell), je peux demander la rigueur quand je le souhaite. Par conséquent, le choix de la langue ne rend pas vraiment vos programmes paresseux ou non, mais affecte simplement ceux que vous obtenez par défaut.

Une fois que vous y réfléchissez de la sorte, pensez à tous les endroits où vous écrivez une structure de données que vous pourrez utiliser ultérieurement pour générer des données (sans trop la regarder auparavant), et vous découvrirez de nombreuses utilisations pour la paresseuse. évaluation.

3
Noah Lavine

Sans évaluation paresseuse, vous ne serez pas autorisé à écrire quelque chose comme ceci:

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

L’exploitation la plus utile de l’évaluation paresseuse que j’ai utilisée est une fonction qui appelle une série de sous-fonctions dans un ordre particulier. Si l'une de ces sous-fonctions échouait (renvoyait la valeur false), la fonction appelante devait immédiatement renvoyer. Donc j'aurais pu le faire de cette façon:

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

(etc)

  return true;
}

ou la solution la plus élégante:

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

Une fois que vous commencez à l'utiliser, vous verrez des possibilités de l'utiliser de plus en plus souvent.

2
Marc Bernier

Entre autres choses, les langages paresseux permettent des structures de données infinies multidimensionnelles.

Alors que schéma, python, etc. autorisent des structures de données infinies à une dimension avec des flux, vous ne pouvez effectuer de déplacement que dans une dimension.

La paresse est utile pour le même problème marginal , mais il convient de noter la connexion de coroutines mentionnée dans ce lien.

2
shapr

L'évaluation paresseuse est le raisonnement équationnel du pauvre (on pourrait s'attendre, idéalement, à déduire des propriétés de code de propriétés de types et d'opérations impliquées). 

Exemple où cela fonctionne assez bien: sum . take 10 $ [1..10000000000]. Ce qui ne nous dérange pas d'être réduit à une somme de 10 nombres, au lieu d'un seul calcul numérique direct et simple. Sans une évaluation paresseuse, cela créerait une liste gigantesque en mémoire, juste pour utiliser ses 10 premiers éléments. Ce serait certainement très lent et pourrait provoquer une erreur de mémoire insuffisante.

Exemple où ce n'est pas aussi génial que nous voudrions: sum . take 1000000 . drop 500 $ cycle [1..20]. Ce qui fera la somme des 1 000 000 numéros, même s’il s’agit d’une boucle plutôt que d’une liste; Néanmoins, il devrait être réduit à un seul calcul numérique direct, avec peu de conditions et peu de formules. Lequel serait serait beaucoup mieux que de résumer les 1 000 000 numéros. Même s’il s’agit d’une boucle et non d’une liste (c’est-à-dire après l’optimisation de la déforestation).


Une autre chose est, il permet de coder dans tail récursion modulo contre style, et il fonctionne seulement.

cf. réponse liée .

2
Will Ness

Si par "évaluation paresseuse", vous entendez comme dans les combats booléens, comme dans 

   if (ConditionA && ConditionB) ... 

alors la réponse est simplement que moins le processeur consomme de cycle de traitement, plus il s'exécutera rapidement ... et si un bloc d'instructions de traitement n'aura aucun impact sur le résultat du programme, il est inutile (et donc inutile). de temps) pour les exécuter quand même ... 

si otoh, vous voulez dire ce que j’ai appelé les "initialiseurs paresseux", comme dans:

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

Cette technique permet au code client qui utilise la classe d’éviter d’appeler la base de données pour l’enregistrement de superviseur, sauf lorsque le client utilisant l’objet Employé requiert un accès aux données du superviseur ... ce qui accélère le processus d’instanciation d’un employé. et pourtant, lorsque vous avez besoin du superviseur, le premier appel à la propriété superviseur déclenchera l'appel de la base de données et les données seront récupérées et disponibles ... 

1
Charles Bretana

Extrait de Fonctions d'ordre supérieur

Trouvons le plus grand nombre inférieur à 100 000 divisible par 3829 . Pour ce faire, nous allons simplement filtrer un ensemble de possibilités dans lesquelles nous savons la solution réside.

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

Nous faisons d’abord une liste de tous les nombres inférieurs à 100 000, par ordre décroissant . Ensuite, nous filtrons par notre prédicat et parce que les nombres sont triés de manière descendante, le plus grand nombre qui satisfait notre prédicat est le premier élément de la liste filtrée. Nous n'avons même pas besoin d'utiliser une liste finie pour notre ensemble de départ. C'est la paresse en action à nouveau. Parce que nous finissons seulement par utiliser la tête du filtre liste, peu importe que la liste filtrée soit finie ou infinie . L'évaluation s'arrête lorsque la première solution adéquate est trouvée.

0
onmyway133