Techniquement, tout programme que vous exécutez sur un ordinateur est impur car il se compile finalement en instructions telles que "déplacer cette valeur dans eax
" et "ajouter cette valeur au contenu de eax
", qui sont impures. Ce n'est pas très utile.
Au lieu de cela, nous pensons à la pureté en utilisant des boîtes noires . Si un code produit toujours les mêmes sorties lorsqu'il reçoit les mêmes entrées, il est considéré comme pur. Selon cette définition, la fonction suivante est également pure même si en interne elle utilise une table de mémo impure.
const fib = (() => {
const memo = [0, 1];
return n => {
if (n >= memo.length) memo[n] = fib(n - 1) + fib(n - 2);
return memo[n];
};
})();
console.log(fib(100));
Nous ne nous soucions pas des composants internes, car nous utilisons une méthodologie de boîte noire pour vérifier la pureté. De même, nous ne nous soucions pas que tout le code soit finalement converti en instructions machine impures parce que nous pensons à la pureté en utilisant une méthodologie de boîte noire. Les internes ne sont pas importants.
Maintenant, considérez la fonction suivante.
const greet = name => {
console.log("Hello %s!", name);
};
greet("World");
greet("Snowman");
La greet
fonction est-elle pure ou impure? Selon notre méthodologie de boîte noire, si nous lui donnons la même entrée (par exemple World
), il imprime toujours la même sortie à l'écran (par exemple Hello World!
). En ce sens, n'est-ce pas pur? Non ce n'est pas. La raison pour laquelle ce n'est pas pur est parce que nous considérons l'impression de quelque chose à l'écran comme un effet secondaire. Si notre boîte noire produit des effets secondaires, elle n'est pas pure.
Qu'est-ce qu'un effet secondaire? C’est là que le concept de transparence référentielle est utile. Si une fonction est référentiellement transparente, nous pouvons toujours remplacer les applications de cette fonction par leurs résultats. Notez que ce n'est pas la même chose que la fonction inline .
Dans la fonction inline, nous remplaçons les applications d'une fonction par le corps de la fonction sans altérer la sémantique du programme. Cependant, une fonction référentiellement transparente peut toujours être remplacée par sa valeur de retour sans altérer la sémantique du programme. Prenons l'exemple suivant.
console.log("Hello %s!", "World");
console.log("Hello %s!", "Snowman");
Ici, nous avons souligné la définition de greet
et cela n'a pas changé la sémantique du programme.
Maintenant, considérez le programme suivant.
Ici, nous avons remplacé les applications du greet
fonction par leurs valeurs de retour et cela a changé la sémantique du programme. Nous n'imprimons plus de salutations à l'écran. C'est la raison pour laquelle l'impression est considérée comme un effet secondaire, et c'est pourquoi la greet
fonction est impure. Ce n'est pas référentiellement transparent.
Maintenant, considérons un autre exemple. Considérez le programme suivant.
const main = async () => {
const response = await fetch("https://time.akamai.com/");
const serverTime = 1000 * await response.json();
const timeDiff = time => time - serverTime;
console.log("%d ms", timeDiff(Date.now()));
};
main();
De toute évidence, la main
fonction est impure. Cependant, la timeDiff
fonction est-elle pure ou impure? Bien que cela dépendeserverTime
ce qui provient d'un appel réseau impur, il est toujours référentiellement transparent car il renvoie les mêmes sorties pour les mêmes entrées et parce qu'il n'a aucun effet secondaire.
zerkms sera probablement en désaccord avec moi sur ce point. Dans sa réponse , il a dit que la dollarToEuro
fonction dans l'exemple suivant est impure parce que "cela dépend transitoirement de l'IO".
const exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x, exchangeRate) => {
return x * exchangeRate;
};
Je dois être en désaccord avec lui parce que le fait que le exchangeRate
provienne d'une base de données n'est pas pertinent. C'est un détail interne et notre méthodologie de boîte noire pour déterminer la pureté d'une fonction ne se soucie pas des détails internes.
Dans les langages purement fonctionnels comme Haskell, nous avons une trappe d'échappement pour exécuter des effets d'E / S arbitraires. C'est appeléunsafePerformIO
, et comme son nom l'indique, si vous ne l'utilisez pas correctement, ce n'est pas sûr, car cela pourrait briser la transparence référentielle. Cependant, si vous savez ce que vous faites, son utilisation est parfaitement sûre.
Il est généralement utilisé pour charger des données à partir de fichiers de configuration au début du programme. Le chargement de données à partir de fichiers de configuration est une opération d'E / S impure. Cependant, nous ne voulons pas être gênés par le passage des données en entrée à chaque fonction. Par conséquent, si nous utilisons unsafePerformIO
alors nous pouvons charger les données au niveau supérieur et toutes nos fonctions pures peuvent dépendre des données de configuration globale immuables.
Notez que ce n'est pas parce qu'une fonction dépend de certaines données chargées à partir d'un fichier de configuration, d'une base de données ou d'un appel réseau que la fonction est impure.
Cependant, considérons votre exemple original qui a une sémantique différente.
let exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x) => {
return x * exchangeRate;
};
dollarToEuro(100) //90 today
dollarToEuro(100) //something else tomorrow
Ici, je suppose que parce que exchangeRate
n'est pas défini comme const
, il va être modifié pendant l'exécution du programme. Si tel est le cas, alors dollarToEuro
c'est définitivement une fonction impure parce que lorsque le exchangeRate
est modifié, il brisera la transparence référentielle.
Cependant, si la exchangeRate
variable n'est pas modifiée et ne sera jamais modifiée à l'avenir (c'est-à-dire si c'est une valeur constante), alors même si elle est définie comme let
, elle ne cassera pas la transparence référentielle. Dans ce cas, dollarToEuro
est en effet une fonction pure.
Notez que la valeur de exchangeRate
peut changer à chaque fois que vous exécutez à nouveau le programme et qu'elle ne cassera pas la transparence référentielle. Il ne rompt la transparence référentielle que s'il change pendant l'exécution du programme.
Par exemple, si vous exécutez mon timeDiff
exemple plusieurs fois, vous obtiendrez des valeurs différentes serverTime
et donc des résultats différents. Toutefois, comme la valeur de serverTime
ne change jamais pendant l'exécution du programme, la timeDiff
fonction est pure.
function myNumber(n) { this.n = n; }; myNumber.prototype.valueOf = function() { console.log('impure'); return this.n; }; const n = new myNumber(42); add(n, 1);