Una funzione che chiama Math.random () è pura?


112

La seguente è una funzione pura?

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

La mia comprensione è che una funzione pura segue queste condizioni:

  1. Restituisce un valore calcolato dai parametri
  2. Non fa altro che calcolare il valore di ritorno

Se questa definizione è corretta, la mia funzione è una funzione pura? O la mia comprensione di ciò che definisce una funzione pura è errata?


66
"Non fa altro che calcolare il valore di ritorno" Ma chiama Math.random()che cambia lo stato del RNG.
Paul Draper

1
Il secondo punto è più simile a "non cambia lo stato esterno (alla funzione)"; e il primo dovrebbe essere completato in qualche modo come "restituisce lo STESSO valore calcolato dagli STESSI parametri", come le persone hanno scritto di seguito
MVCDS

Esiste la nozione di una funzione semipura che consenta la casualità? Ad esempio test(a,b)restituisce sempre lo stesso oggetto Random(a,b)(che può rappresentare numeri concreti diversi)? Se mantieni il Randomsimbolico è puro in senso classico, se lo valuti presto e lo metti in numeri, magari come una sorta di ottimizzazione, la funzione conserva comunque una certa "purezza".
jdm

1
"Chiunque consideri metodi aritmetici per produrre cifre casuali è, ovviamente, in uno stato di peccato." - John von Neumann
Steve Kuo

1
@jdm se segui il filo del "semi-puro", dove consideri le funzioni pure modulo alcuni effetti collaterali ben definiti , potresti finire per inventare le monadi. Benvenuto nel lato oscuro. > :)
luqui

Risposte:


185

No non lo è. Dato lo stesso input, questa funzione restituirà valori diversi. E poi non puoi costruire una "tabella" che mappa l'input e gli output.

Dall'articolo di Wikipedia per la funzione Pure :

La funzione valuta sempre lo stesso valore di risultato dato gli stessi valori di argomento. Il valore del risultato della funzione non può dipendere da alcuna informazione o stato nascosto che può cambiare durante l'esecuzione del programma o tra diverse esecuzioni del programma, né può dipendere da alcun input esterno dai dispositivi I / O

Inoltre, un'altra cosa è che una funzione pura può essere sostituita con una tabella che rappresenta la mappatura dall'input e dall'output, come spiegato in questo thread .

Se vuoi riscrivere questa funzione e cambiarla in una funzione pura, dovresti passare anche il valore casuale come argomento

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

e poi chiamalo in questo modo (esempio, con 2 e 5 come minimo e massimo):

test( Math.random(), 2, 5)

2
E se dovessi rieseguire il seed del generatore casuale ogni volta all'interno della funzione prima di chiamare Math.random?
cs95

16
@ cᴏʟᴅsᴘᴇᴇᴅ Anche allora, avrebbe ancora effetti collaterali (cambiando la Math.randomproduzione futura ); affinché sia ​​puro, dovresti in qualche modo salvare lo stato RNG corrente, resecarlo, richiamarlo Math.randome ripristinarlo allo stato precedente.
LegionMammal978

2
@ cᴏʟᴅsᴘᴇᴇᴅ Tutto il RNG calcolato è basato sulla falsa casualità. Deve essere in esecuzione qualcosa al di sotto che lo faccia apparire casuale e non puoi spiegarlo, rendendolo impuro. Inoltre, e probabilmente più importante per la tua domanda, non puoi seed Math.random
zfrisch

14
@ LegionMammal978 ... e fallo in modo atomico.
wchargin

2
@ cᴏʟᴅsᴘᴇᴇᴅ Ci sono modi per avere RNG che funzionano con funzioni pure, ma implica il passaggio dello stato RNG alla funzione e che la funzione restituisca lo stato RNG sostitutivo, che è il modo in cui Haskell (un linguaggio di programmazione funzionale che impone la purezza funzionale) esso.
Pharap

50

La semplice risposta alla tua domanda è che Math.random()viola la regola n. 2.

Molte altre risposte qui hanno sottolineato che la presenza di Math.random()significa che questa funzione non è pura. Ma penso che valga la pena spiegare perché Math.random() contamina le funzioni che lo utilizzano.

Come tutti i generatori di numeri pseudocasuali, Math.random()inizia con un valore "seme". Quindi utilizza quel valore come punto di partenza per una catena di manipolazioni di bit di basso livello o altre operazioni che risultano in un output imprevedibile (ma non realmente casuale ).

In JavaScript, il processo coinvolto dipende dall'implementazione e, a differenza di molti altri linguaggi, JavaScript fornisce non alcun modo per selezionare il seme :

L'implementazione seleziona il seme iniziale per l'algoritmo di generazione di numeri casuali; non può essere scelto o ripristinato dall'utente.

Ecco perché questa funzione non è pura: JavaScript utilizza essenzialmente un parametro di funzione implicito su cui non hai alcun controllo. Sta leggendo quel parametro dai dati calcolati e memorizzati altrove e quindi viola la regola n. 2 nella tua definizione.

Se si desidera rendere questa funzione pura, è possibile utilizzare uno dei generatori di numeri casuali alternativi descritti qui . Chiama quel generatore seedable_random. Richiede un parametro (il seme) e restituisce un numero "casuale". Naturalmente, questo numero non è affatto casuale; è determinato in modo univoco dal seme. Ecco perché questa è una funzione pura. L'output di seedable_randomè solo "casuale" nel senso che è difficile prevedere l'output in base all'input.

La versione pura di questa funzione dovrebbe prendere tre parametri:

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

Per ogni dato triplo di (min, max, seed)parametri, questo restituirà sempre lo stesso risultato.

Nota che se vuoi che l'output di seedable_randomsia veramente casuale, devi trovare un modo per randomizzare il seme! E qualunque strategia tu abbia usato sarebbe inevitabilmente non pura, perché ti richiederebbe di raccogliere informazioni da una fonte al di fuori della tua funzione. Come mi ricordano mtraceur e jpmc26 , questo include tutti gli approcci fisici: generatori di numeri casuali hardware , webcam con copriobiettivo , raccoglitori di rumore atmosferico e persino lampade lava . Tutto ciò comporta l'utilizzo di dati calcolati e memorizzati al di fuori della funzione.


8
Math.random () non solo legge il suo "seme", ma lo modifica, in modo che la chiamata successiva restituisca qualcosa di diverso. Dipendere e modificare lo stato statico è decisamente negativo per una funzione pura.
Nate Eldredge

2
@NateEldredge, proprio così! Sebbene la semplice lettura di un valore dipendente dall'implementazione sia sufficiente per rompere la purezza. Ad esempio, hai mai notato come gli hash di Python 3 non siano stabili tra i processi?
mittente

2
Come cambierebbe questa risposta se Math.randomnon si usasse un PRNG ma fosse invece implementato utilizzando un RNG hardware? L'RNG hardware non ha realmente uno stato nel senso normale, ma produce valori casuali (e quindi l'output della funzione è ancora diverso indipendentemente dall'input), giusto?
mtraceur

@mtraceur, è corretto. Ma non credo che la risposta cambierebbe molto. In effetti, questo è il motivo per cui non passo il tempo a parlare di "stato" nella mia risposta. Leggere da un RNG hardware significa anche leggere da "dati calcolati e memorizzati altrove". È solo che i dati vengono calcolati e memorizzati nel supporto fisico del computer stesso mentre interagisce con il suo ambiente.
mittente

1
Questa stessa logica si applica anche a schemi di randomizzazione più sofisticati, anche a quelli come il rumore atmosferico di Random.org . +1
jpmc26

38

Una funzione pura è una funzione in cui il valore restituito è determinato solo dai suoi valori di input, senza effetti collaterali osservabili

Usando Math.random, stai determinando il suo valore da qualcosa di diverso dai valori di input. Non è una funzione pura.

fonte


25

No, non è una funzione pura perché il suo output non dipende solo dall'input fornito (Math.random () può restituire qualsiasi valore), mentre le funzioni pure dovrebbero sempre restituire lo stesso valore per gli stessi input.

Se una funzione è pura, è sicuro ottimizzare più chiamate con gli stessi input e riutilizzare semplicemente il risultato di una chiamata precedente.

PS almeno per me e per molti altri, redux ha reso popolare il termine funzione pura . Direttamente dai documenti redux :

Cose che non dovresti mai fare all'interno di un riduttore:

  • Mutare i suoi argomenti;

  • Eseguire effetti collaterali come chiamate API e transizioni di instradamento;

  • Chiama funzioni non pure, ad esempio Date.now () o Math.random ().


3
Sebbene altri abbiano fornito ottime risposte, ma non ho potuto resistere a me stesso quando mi sono venuti in mente i documenti di redux e Math.random () menzionato specificamente in essi :)
Shubhnik Singh

20

Dal punto di vista matematico, la tua firma non lo è

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

ma

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

dove environmentè in grado di fornire risultati di Math.random(). E in realtà la generazione del valore casuale muta l'ambiente come effetto collaterale, quindi restituisci anche un nuovo ambiente, che non è uguale al primo!

In altre parole, se hai bisogno di qualsiasi tipo di input che non provenga dagli argomenti iniziali (la <number, number>parte), allora devi essere fornito con l'ambiente di esecuzione (che in questo esempio fornisce lo stato Math). Lo stesso vale per altre cose menzionate da altre risposte, come I / O o simili.


Come analogia, puoi anche notare che è così che può essere rappresentata la programmazione orientata agli oggetti - se diciamo, ad es

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

allora effettivamente stiamo usando

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

con l'oggetto che ha il suo metodo invocato come parte dell'ambiente. E perché la SomeClassparte del risultato? Perché anche lo somethingstato potrebbe essere cambiato!


7
Peggio ancora, anche l'ambiente è mutato, quindi test: <environment, number, number> -> <environment, number>dovrebbe essere
Bergi

1
Non sono sicuro che l'esempio OO sia molto simile. a.F(b, c)può essere visto come zucchero sintattico per F(a, b, c)con una regola speciale da inviare a definizioni sovraccariche di Fbasate sul tipo di a(questo è in realtà come lo rappresenta Python). Ma aè ancora esplicito in entrambe le notazioni, mentre l'ambiente in una funzione non pura non è mai menzionato nel codice sorgente.
IMSoP


10

Oltre alle altre risposte che indicano correttamente come questa funzione sia non deterministica, ha anche un effetto collaterale: farà sì che le chiamate future math.random()a restituiscano una risposta diversa. E un generatore di numeri casuali che non dispone di tale proprietà generalmente eseguirà un qualche tipo di I / O, ad esempio leggere da un dispositivo casuale fornito dal sistema operativo. O è verboten per una funzione pura.


7

No, non lo è. Non puoi assolutamente capire il risultato, quindi questo pezzo di codice non può essere testato. Per rendere quel codice testabile, è necessario estrarre il componente che genera il numero casuale:

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

Ora puoi deridere il generatore e testare correttamente il tuo codice:

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

E nel tuo codice di "produzione":

const result = test(1, 2, Math.random);

1
▲ per il tuo pensiero sulla testabilità. Con un po 'di attenzione puoi anche produrre test ripetibili accettando a util.Random, che puoi seminare all'inizio di un'esecuzione di test per ripetere il vecchio comportamento o per una nuova (ma ripetibile) esecuzione. Se il multi-threading, potresti essere in grado di farlo nel thread principale e usarlo Randomper seminare i thread locali ripetibili Random. Tuttavia, a quanto ho capito, test(int,int,Random)non è considerato puro in quanto altera lo stato del Random.
PJTraill

2

Staresti bene con quanto segue:

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

essere equivalente a

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

?

Vedete, la definizione di puro è una funzione il cui output non cambia con nient'altro che i suoi input. Se diciamo che JavaScript ha un modo per taggare una funzione pura e trarne vantaggio, l'ottimizzatore sarebbe autorizzato a riscrivere la prima espressione come seconda.

Ho esperienza pratica con questo. Il server SQL ha consentito getdate()e newid()in funzioni "pure" e l'ottimizzatore deduplica le chiamate a piacimento. A volte questo farebbe qualcosa di stupido.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.