Une fonction qui appelle Math.random () est-elle pure?

112

Est-ce que ce qui suit est une fonction pure?

function test(min,max) {
   return  Math.random() * (max - min) + min;
}

Ma compréhension est qu'une fonction pure suit ces conditions:

  1. Il renvoie une valeur calculée à partir des paramètres
  2. Il ne fait aucun travail autre que le calcul de la valeur de retour

Si cette définition est correcte, ma fonction est-elle une fonction pure? Ou est-ce que ma compréhension de ce qui définit une fonction pure est incorrecte?

Kiwi Rupela
la source
66
"Il ne fait aucun travail autre que le calcul de la valeur de retour" Mais il appelle Math.random()ce qui change l'état du RNG.
Paul Draper
1
Le deuxième point est plutôt "il ne change pas d'état externe (à la fonction)"; et le premier devrait être complété un peu comme "il renvoie la MÊME valeur calculée à partir des mêmes paramètres", comme les gens l'ont écrit ci
MVCDS
Existe-t-il une notion de fonction semi-pure, permettant le caractère aléatoire? Par exemple, test(a,b)renvoie toujours le même objet Random(a,b)(qui peut représenter différents nombres concrets)? Si vous gardez Randomsymbolique, il est pur au sens classique, si vous l'évaluez tôt et le mettez en chiffres, peut-être comme une sorte d'optimisation, la fonction conserve encore une certaine «pureté».
jdm
1
"Quiconque considère les méthodes arithmétiques de production de chiffres aléatoires est, bien sûr, dans un état de péché." - John von Neumann
Steve Kuo
1
@jdm si vous suivez le fil de "semi-pure", où vous considérez les fonctions pures modulo comme des effets secondaires bien définis , vous pourriez finir par inventer des monades. Bienvenue dans le côté obscur. > :)
luqui

Réponses:

185

Non ce n'est pas. Étant donné la même entrée, cette fonction renverra des valeurs différentes. Et puis vous ne pouvez pas construire une «table» qui mappe l'entrée et les sorties.

Extrait de l'article Wikipedia sur la fonction Pure :

La fonction évalue toujours la même valeur de résultat avec la ou les mêmes valeurs d'argument. La valeur du résultat de la fonction ne peut dépendre d'aucune information cachée ou d'un état pouvant changer pendant l'exécution du programme ou entre différentes exécutions du programme, ni d'une entrée externe des périphériques d'E / S

De plus, une autre chose est qu'une fonction pure peut être remplacée par une table qui représente le mappage de l'entrée et de la sortie, comme expliqué dans ce thread .

Si vous voulez réécrire cette fonction et la changer en une fonction pure, vous devez également passer la valeur aléatoire comme argument

function test(random, min, max) {
   return random * (max - min) + min;
}

puis appelez-le de cette façon (exemple, avec 2 et 5 comme min et max):

test( Math.random(), 2, 5)
Christian Benseler
la source
2
Et si vous réinitialisiez le générateur aléatoire à chaque fois dans la fonction avant d'appeler Math.random?
cs95 du
16
@ cᴏʟᴅsᴘᴇᴇᴅ Même dans ce cas, cela aurait encore des effets secondaires (modification de la Math.randomsortie future ); pour qu'il soit pur, vous devez en quelque sorte enregistrer l'état RNG actuel, le réamorcer, l'appeler Math.randomet le restaurer à l'état précédent.
LegionMammal978 du
2
@ cᴏʟᴅsᴘᴇᴇᴅ Tous les RNG calculés sont basés sur un faux hasard. Quelque chose doit être en cours d'exécution en dessous qui le fait apparaître aléatoire et vous ne pouvez pas en tenir compte, ce qui le rend impur. De plus, et probablement plus important pour votre question, vous ne pouvez pas semer Math.random
zfrisch
14
@ LegionMammal978… et faites-le de manière atomique.
wchargin
2
@ cᴏʟᴅsᴘᴇᴇᴅ Il existe des moyens pour que les RNG fonctionnent avec des fonctions pures, mais cela implique de passer l'état RNG à la fonction et de faire en sorte que la fonction renvoie l'état RNG de remplacement, ce qui permet à Haskell (un langage de programmation fonctionnel qui renforce la pureté fonctionnelle) il.
Pharap
50

La réponse simple à votre question est que cela Math.random()enfreint la règle n ° 2.

Beaucoup d'autres réponses ici ont souligné que la présence de Math.random()signifie que cette fonction n'est pas pure. Mais je pense que cela vaut la peine de dire pourquoi Math.random() ternit les fonctions qui l'utilisent.

Comme tous les générateurs de nombres pseudo-aléatoires, Math.random()commence par une valeur «de départ». Il utilise ensuite cette valeur comme point de départ pour une chaîne de manipulations de bits de bas niveau ou d'autres opérations qui aboutissent à une sortie imprévisible (mais pas vraiment aléatoire ).

En JavaScript, le processus impliqué dépend de l'implémentation et, contrairement à de nombreux autres langages, JavaScript ne permet pas de sélectionner la graine :

L'implémentation sélectionne la graine initiale de l'algorithme de génération de nombres aléatoires; il ne peut pas être choisi ou réinitialisé par l'utilisateur.

C'est pourquoi cette fonction n'est pas pure: JavaScript utilise essentiellement un paramètre de fonction implicite sur lequel vous n'avez aucun contrôle. Il lit ce paramètre à partir de données calculées et stockées ailleurs, et enfreint donc la règle n ° 2 de votre définition.

Si vous souhaitez en faire une fonction pure, vous pouvez utiliser l'un des générateurs de nombres aléatoires alternatifs décrits ici . Appelez ce générateur seedable_random. Il prend un paramètre (la graine) et renvoie un nombre "aléatoire". Bien sûr, ce nombre n'est pas du tout aléatoire; il est uniquement déterminé par la graine. C'est pourquoi c'est une fonction pure. La sortie de seedable_randomest uniquement "aléatoire" dans le sens où la prédiction de la sortie basée sur l'entrée est difficile.

La version pure de cette fonction aurait besoin de prendre trois paramètres:

function test(min, max, seed) {
   return  seedable_random(seed) * (max - min) + min;
}

Pour tout triple de (min, max, seed)paramètres donné, cela retournera toujours le même résultat.

Notez que si vous voulez que la sortie de seedable_randomsoit vraiment aléatoire, vous devez trouver un moyen de randomiser la graine! Et quelle que soit la stratégie que vous utilisiez serait inévitablement non pure, car elle vous obligerait à collecter des informations auprès d'une source extérieure à votre fonction. Comme mtraceur et jpmc26 me le rappellent, cela inclut toutes les approches physiques: générateurs de nombres aléatoires matériels , webcams avec bouchons d'objectif , collecteurs de bruit atmosphérique - même lampes à lave . Tous ces éléments impliquent l'utilisation de données calculées et stockées en dehors de la fonction.

expéditeur
la source
8
Math.random () lit non seulement sa "graine", mais la modifie également, de sorte que le prochain appel retournera quelque chose de différent. Selon, et en modifiant, l'état statique est définitivement mauvais pour une fonction pure.
Nate Eldredge
2
@NateEldredge, tout à fait! Bien que la simple lecture d'une valeur dépendante de l'implémentation soit suffisante pour briser la pureté. Par exemple, avez-vous déjà remarqué que les hachages Python 3 ne sont pas stables entre les processus?
senderle
2
Comment cette réponse changerait-elle si vous Math.randomn'utilisiez pas de PRNG mais était plutôt implémentée à l'aide d'un RNG matériel? Le RNG matériel n'a pas vraiment d'état au sens normal du terme, mais il produit des valeurs aléatoires (et donc la sortie de fonction est toujours différente quelle que soit l'entrée), non?
mtraceur
@mtraceur, c'est correct. Mais je ne pense pas que la réponse changerait beaucoup. En fait, c'est pourquoi je ne passe pas de temps à parler d '«état» dans ma réponse. Lire à partir d'un RNG matériel signifie aussi lire à partir de «données calculées et stockées ailleurs». C'est juste que les données sont calculées et stockées sur le support physique de l'ordinateur lui-même car il interagit avec son environnement.
senderle
1
Cette même logique s'applique même aux schémas de randomisation plus sophistiqués, même ceux comme le bruit atmosphérique de Random.org . +1
jpmc26
38

Une fonction pure est une fonction où la valeur de retour n'est déterminée que par ses valeurs d'entrée, sans effets secondaires observables

En utilisant Math.random, vous déterminez sa valeur par autre chose que des valeurs d'entrée. Ce n'est pas une fonction pure.

la source

TKoL
la source
25

Non, ce n'est pas une fonction pure car sa sortie ne dépend pas uniquement de l'entrée fournie (Math.random () peut générer n'importe quelle valeur), alors que les fonctions pures devraient toujours afficher la même valeur pour les mêmes entrées.

Si une fonction est pure, il est prudent d'optimiser plusieurs appels avec les mêmes entrées et de simplement réutiliser le résultat d'un appel précédent.

PS pour moi au moins et pour beaucoup d'autres, redux a rendu populaire le terme fonction pure . Directement à partir de la documentation redux :

Choses que vous ne devriez jamais faire à l'intérieur d'un réducteur:

  • Muter ses arguments;

  • Effectuer des effets secondaires tels que les appels API et les transitions de routage;

  • Appelez des fonctions non pures, par exemple Date.now () ou Math.random ().

Shubhnik Singh
la source
3
Bien que d'autres aient fourni d'excellentes réponses, mais je n'ai pas pu me résister quand je me suis souvenu de redux doc et que Math.random () y était spécifiquement mentionné :)
Shubhnik Singh
20

Du point de vue mathématique, votre signature n'est pas

test: <number, number> -> <number>

mais

test: <environment, number, number> -> <environment, number>

où le environmentest capable de fournir des résultats Math.random(). Et la génération de la valeur aléatoire entraîne une mutation de l'environnement en tant qu'effet secondaire, de sorte que vous retournez également un nouvel environnement, qui n'est pas égal au premier!

En d'autres termes, si vous avez besoin d'un type d'entrée qui ne provient pas des arguments initiaux (la <number, number>partie), vous devez disposer d'un environnement d'exécution (qui dans cet exemple fournit un état Math). La même chose s'applique à d'autres choses mentionnées par d'autres réponses, comme les E / S ou autres.


Par analogie, vous pouvez également remarquer que c'est ainsi que la programmation orientée objet peut être représentée - si nous disons, par exemple

SomeClass something
T result = something.foo(x, y)

alors en fait nous utilisons

foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>

avec l'objet dont la méthode est invoquée faisant partie de l'environnement. Et pourquoi la SomeClasspart de résultat? Parce que son somethingétat aurait pu changer aussi!

Adam Kotwasinski
la source
7
Pire encore, l'environnement est également muté, test: <environment, number, number> -> <environment, number>il devrait donc l'être
Bergi
1
Je ne suis pas sûr que l'exemple OO soit très similaire. a.F(b, c)peut être considéré comme du sucre syntaxique F(a, b, c)avec une règle spéciale à envoyer aux définitions surchargées de en Ffonction du type de a(c'est en fait ainsi que Python le représente). Mais aest toujours explicite dans les deux notations, alors que l'environnement dans une fonction non pure n'est jamais mentionné dans le code source.
IMSoP
11

Les fonctions pures renvoient toujours la même valeur pour la même entrée. Les fonctions pures sont prévisibles et transparentes référentielles, ce qui signifie que nous pouvons remplacer l'appel de fonction par la sortie renvoyée et cela ne changera pas le fonctionnement du programme.

https://github.com/MostlyAdequate/mostly- CORRECTate-guide/blob/master/ch3.md

Rishabh Mishra
la source
10

En plus des autres réponses qui soulignent correctement en quoi cette fonction est non déterministe, elle a également un effet secondaire: elle entraînera les futurs appels math.random()à renvoyer une réponse différente. Et un générateur de nombres aléatoires qui n'a pas cette propriété effectuera généralement une sorte d'E / S, comme la lecture à partir d'un périphérique aléatoire fourni par le système d'exploitation. Soit est verboten pour une fonction pure.

Davislor
la source
7

Non, ça ne l'est pas. Vous ne pouvez pas du tout comprendre le résultat, donc ce morceau de code ne peut pas être testé. Pour rendre ce code testable, vous devez extraire le composant qui génère le nombre aléatoire:

function test(min, max, generator) {
  return  generator() * (max - min) + min;
}

Maintenant, vous pouvez vous moquer du générateur et tester correctement votre code:

const result = test(1, 2, () => 3);
result == 4 //always true

Et dans votre code "production":

const result = test(1, 2, Math.random);
Hector
la source
1
▲ pour votre pensée pour la testabilité. Avec un peu de soin, vous pouvez également produire des tests répétables tout en acceptant a util.Random, que vous pouvez amorcer au début d'une exécution de test pour répéter l'ancien comportement ou pour une nouvelle (mais répétable) exécution. En cas de multi-threading, vous pourrez peut-être le faire dans le thread principal et l'utiliser Randompour amorcer des threads locaux répétables Random. Cependant, si je comprends bien, test(int,int,Random)n'est pas considéré comme pur car il modifie l'état du Random.
PJTraill
2

Seriez-vous d'accord avec ce qui suit:

return ("" + test(0,1)) + test(0,1);

être équivalent à

var temp = test(0, 1);
return ("" + temp) + temp;

?

Vous voyez, la définition de pure est une fonction dont la sortie ne change pas avec autre chose que ses entrées. Si nous disons que JavaScript avait un moyen de marquer une fonction pure et d'en tirer parti, l'optimiseur serait autorisé à réécrire la première expression en tant que seconde.

J'ai une expérience pratique dans ce domaine. Serveur SQL autorisé getdate()et newid()dans les fonctions "pures" et l'optimiseur dédupliquerait les appels à volonté. Parfois, cela ferait quelque chose de stupide.

Joshua
la source