web-dev-qa-db-fra.com

Fonctions pures: "Pas d'effets secondaires" signifie-t-il toujours "Même sortie, même entrée"?

Les deux conditions qui définissent une fonction comme pure sont les suivantes:

  1. Aucun effet secondaire (c'est-à-dire que seuls les changements de portée locale sont autorisés)
  2. Renvoie toujours la même sortie, étant donné la même entrée

Si la première condition est toujours vraie, y a-t-il des fois où la deuxième condition n'est pas vraie?

C'est à dire. est-ce vraiment seulement nécessaire avec la première condition?

83
Magnus

Voici quelques contre-exemples qui ne modifient pas la portée externe mais sont toujours considérés comme impurs:

  • function a() { return Date.now(); }
  • function b() { return window.globalMutableVar; }
  • function c() { return document.getElementById("myInput").value; }
  • function d() { return Math.random(); } (qui, certes, modifie le PRNG, mais n'est pas considéré comme observable)

L'accès à des variables non locales non constantes est suffisant pour pouvoir violer la deuxième condition.

Je pense toujours que les deux conditions de pureté sont complémentaires:

  • l'évaluation des résultats ne doit pas avoir d'effets sur l'état latéral
  • le résultat de l'évaluation ne doit pas être affecté par l'état secondaire

Le terme effet secondaire fait uniquement référence au premier, la fonction modifiant l'état non local. Cependant, les opérations de lecture sont parfois également considérées comme des effets secondaires: lorsqu'elles sont opérations et impliquent également l'écriture, même si leur objectif principal est d'accéder à une valeur. Des exemples pour cela sont la génération d'un nombre pseudo-aléatoire qui modifie l'état interne du générateur, la lecture à partir d'un flux d'entrée qui fait avancer la position de lecture, ou la lecture à partir d'un capteur externe qui implique une commande "prendre une mesure".

113
Bergi

La façon "normale" de formuler ce qu'est une fonction pure, est en termes de transparence référentielle. Une fonction est pure si elle est référentiellement transparente.

Transparence référentielle, en gros, signifie que vous pouvez remplacer l'appel à la fonction par sa valeur de retour ou vice versa à tout moment du programme, sans changer la signification du programme.

Ainsi, par exemple, si les printf de C étaient référentiellement transparents, ces deux programmes devraient avoir la même signification:

printf("Hello");

et

5;

et tous les programmes suivants doivent avoir la même signification:

5 + 5;

printf("Hello") + 5;

printf("Hello") + printf("Hello");

Parce que printf renvoie le nombre de caractères écrits, dans ce cas 5.

Cela devient encore plus évident avec les fonctions void. Si j'ai une fonction void foo, puis

foo(bar, baz, quux);

devrait être le même que

;

C'est à dire. puisque foo ne renvoie rien, je devrais pouvoir le remplacer par rien sans changer la signification du programme.

Il est donc clair que ni printf ni foo ne sont référentiellement transparents, et donc ni l'un ni l'autre ne sont purs. En fait, une fonction void ne peut jamais être référentiellement transparente, sauf s'il s'agit d'un no-op.

Je trouve cette définition beaucoup plus facile à gérer que celle que vous avez donnée. Il vous permet également de l'appliquer à n'importe quelle granularité souhaitée: vous pouvez l'appliquer à des expressions individuelles, à des fonctions, à des programmes entiers. Il vous permet, par exemple, de parler d'une fonction comme celle-ci:

func fib(n):
    return memo[n] if memo.has_key?(n)
    return 1 if n <= 1
    return memo[n] = fib(n-1) + fib(n-2)

Nous pouvons analyser les expressions qui composent la fonction et conclure facilement qu'elles ne sont pas référentiellement transparentes et donc pas pures, car elles utilisent une structure de données mutable, à savoir le tableau memo. Cependant, nous pouvons également regarder la fonction et voir qu'elle est référentiellement transparente et donc pure. Ceci est parfois appelé pureté externe, c'est-à-dire une fonction qui apparaît pure au monde extérieur, mais qui est implémentée impure en interne.

De telles fonctions sont toujours utiles, car alors que l'impureté infecte tout ce qui l'entoure, l'interface pure externe construit une sorte de "barrière de pureté", où l'impureté infecte uniquement les trois lignes de la fonction, mais ne s'échappe pas dans le reste du programme. . Ces trois lignes sont beaucoup plus faciles à analyser pour l'exactitude que l'ensemble du programme.

29
Jörg W Mittag

Il me semble que la deuxième condition que vous avez décrite est une contrainte plus faible que la première.

Permettez-moi de vous donner un exemple, supposons que vous ayez une fonction pour en ajouter une qui se connecte également à la console:

function addOneAndLog(x) {
  console.log(x);
  return x + 1;
}

La deuxième condition que vous avez fournie est satisfaite: cette fonction renvoie toujours la même sortie lorsqu'elle reçoit la même entrée. Ce n'est cependant pas une fonction pure car elle inclut l'effet secondaire de la connexion à la console.

Une fonction pure est, à proprement parler, une fonction qui satisfait la propriété de transparence référentielle . C'est la propriété que nous pouvons remplacer une application de fonction par la valeur qu'elle produit sans changer le comportement du programme.

Supposons que nous ayons une fonction qui ajoute simplement:

function addOne(x) {
  return x + 1;
}

Nous pouvons remplacer addOne(5) par 6 N'importe où dans notre programme et rien ne changera.

En revanche, nous ne pouvons pas remplacer addOneAndLog(x) par la valeur 6 N'importe où dans notre programme sans changer de comportement car la première expression entraîne l'écriture de quelque chose sur la console alors que la seconde ne le fait pas.

Nous considérons tout ce comportement supplémentaire que addOneAndLog(x) effectue en plus de renvoyer la sortie comme un effet secondaire .

12
TheInnerLight

Il pourrait y avoir une source de hasard de l'extérieur du système. Supposons qu'une partie de votre calcul inclut la température ambiante. Ensuite, l'exécution de la fonction donnera des résultats différents à chaque fois en fonction de l'élément externe aléatoire de la température ambiante. L'état n'est pas modifié en exécutant le programme.

Tout ce que je peux penser, de toute façon.

7
user3340459

Le problème avec les définitions FP est qu'elles sont très artificielles. Chaque évaluation/calcul a des effets secondaires sur l'évaluateur. C'est théoriquement vrai. Un refus de cela montre seulement que FP les apologistes ignorent la philosophie et la logique: une "évaluation" signifie un changement de l'état d'un environnement intelligent (machine, cerveau, etc.). Telle est la nature du processus d'évaluation. Pas de changement - pas de "calcul". L'effet peut être très visible: chauffer le CPU ou sa panne, éteindre la carte mère en cas de surchauffe, etc.

Lorsque vous parlez de transparence référentielle, vous devez comprendre que les informations sur cette transparence sont disponibles pour l'homme en tant que créateur de l'ensemble du système et détenteur d'informations sémantiques et peuvent ne pas être disponibles pour le compilateur. Par exemple, une fonction peut lire des ressources externes et elle aura IO monade dans sa signature mais elle renverra tout le temps la même valeur (par exemple, le résultat de current_year > 0). Le compilateur ne sait pas que la fonction retournera toujours le même résultat, donc la fonction est impure mais a une propriété référentiellement transparente et peut être remplacée par True constant.

Ainsi, pour éviter une telle imprécision, nous devons distinguer les fonctions mathématiques et les "fonctions" dans les langages de programmation. Les fonctions dans Haskell sont toujours impures et la définition de la pureté qui leur est liée est toujours très conditionnelle: elles fonctionnent sur du matériel réel avec des effets secondaires réels et des propriétés physiques, ce qui est faux pour les fonctions mathématiques. Cela signifie que l'exemple avec la fonction "printf" est totalement incorrect.

Mais toutes les fonctions mathématiques ne sont pas aussi pures: chaque fonction qui a t (temps) comme paramètre peut être impure: t contient tous les effets et la nature stochastique de la fonction: dans le cas commun, vous avez signal d'entrée et n'ont aucune idée des valeurs réelles, cela peut même être un bruit.

2
Paul-AG

Si la première condition est toujours vraie, y a-t-il des fois où la deuxième condition n'est pas vraie?

Oui

Considérez l'extrait de code simple ci-dessous

public int Sum(int a, int b) {
    Random rnd = new Random();
    return rnd.Next(1, 10);
}

Ce code renverra une sortie aléatoire pour le même ensemble donné d'entrées - mais il n'a aucun effet secondaire.

L'effet global des deux points # 1 et # 2 que vous avez mentionnés lorsqu'ils sont combinés ensemble signifie: À tout moment si la fonction Sum avec le même i/p est remplacée par son résultat dans un programme, la signification globale du programme ne change pas . Ce n'est rien d'autre que Transparence référentielle .

2
rahulaga_dev