Ist eine Funktion, die Math.random () aufruft, rein?

112

Ist das Folgende eine reine Funktion?

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

Mein Verständnis ist, dass eine reine Funktion diesen Bedingungen folgt:

  1. Es gibt einen Wert zurück, der aus den Parametern berechnet wurde
  2. Es macht keine andere Arbeit als die Berechnung des Rückgabewerts

Wenn diese Definition korrekt ist, ist meine Funktion eine reine Funktion? Oder ist mein Verständnis dessen, was eine reine Funktion definiert, falsch?

Kiwi Rupela
quelle
66
"Es macht keine andere Arbeit als die Berechnung des Rückgabewerts." Aber es ruft auf, Math.random()was den Status des RNG ändert.
Paul Draper
1
Der zweite Punkt ist eher wie "es ändert nicht den externen (in den Funktions-) Zustand"; und das erste sollte etwas ergänzt werden wie "es gibt den gleichen Wert zurück, der aus den gleichen Parametern berechnet wurde", wie die Leute unten geschrieben haben
MVCDS
Gibt es eine Vorstellung von einer Semipure-Funktion, die Zufälligkeit zulässt? ZB gibt test(a,b)immer das gleiche Objekt zurück Random(a,b)(das unterschiedliche konkrete Zahlen darstellen kann)? Wenn Sie Randomsymbolisch bleiben , ist es im klassischen Sinne rein, wenn Sie es früh bewerten und Zahlen eingeben, vielleicht als eine Art Optimierung, behält die Funktion immer noch eine gewisse "Reinheit".
JDM
1
"Jeder, der arithmetische Methoden zur Erzeugung zufälliger Ziffern in Betracht zieht, befindet sich natürlich in einem Zustand der Sünde." - John von Neumann
Steve Kuo
1
@jdm Wenn Sie dem Thread von "semi-pure" folgen, in dem Sie Funktionen pure modulo als einige genau definierte Nebenwirkungen betrachten, werden Sie möglicherweise Monaden erfinden. Willkommen auf der dunklen Seite. > :)
Luqui

Antworten:

185

Nein, ist es nicht. Bei gleicher Eingabe gibt diese Funktion unterschiedliche Werte zurück. Und dann können Sie keine 'Tabelle' erstellen, die die Eingabe und die Ausgaben abbildet.

Aus dem Wikipedia-Artikel für Pure function :

Die Funktion wertet immer den gleichen Ergebniswert bei gleichen Argumentwerten aus. Der Funktionsergebniswert kann weder von versteckten Informationen oder Zuständen abhängen, die sich während der Programmausführung oder zwischen verschiedenen Programmausführungen ändern können, noch von externen Eingaben von E / A-Geräten

Eine andere Sache ist, dass eine reine Funktion durch eine Tabelle ersetzt werden kann, die die Zuordnung von Eingabe und Ausgabe darstellt, wie in diesem Thread erläutert .

Wenn Sie diese Funktion umschreiben und in eine reine Funktion ändern möchten, sollten Sie den Zufallswert auch als Argument übergeben

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

und nenne es dann so (Beispiel mit 2 und 5 als min und max):

test( Math.random(), 2, 5)
Christian Benseler
quelle
2
Was wäre, wenn Sie den Zufallsgenerator jedes Mal innerhalb der Funktion neu setzen würden, bevor Sie ihn aufrufen Math.random?
CS95
16
@ cᴏʟᴅsᴘᴇᴇᴅ Selbst dann hätte es immer noch Nebenwirkungen (Änderung der zukünftigen Math.randomAusgabe); Damit es rein ist, müssten Sie den aktuellen RNG-Status irgendwie speichern, neu säen, aufrufen Math.randomund auf den vorherigen Status zurücksetzen .
LegionMammal978
2
@ cᴏʟᴅsᴘᴇᴇᴅ Alle berechneten RNG basieren auf gefälschter Zufälligkeit. Darunter muss etwas laufen, das dazu führt, dass es zufällig erscheint, und das kann man nicht erklären, was es unrein macht. Auch und wahrscheinlich wichtiger für Ihre Frage, können Sie Math.random
zfrisch
14
@ LegionMammal978… und das atomar.
wchargin
2
@ cᴏʟᴅsᴘᴇᴇᴅ Es gibt Möglichkeiten, RNGs zu haben, die mit reinen Funktionen arbeiten, aber es geht darum, den RNG-Status an die Funktion zu übergeben und die Funktion den Ersatz-RNG-Status zurückgeben zu lassen. Auf diese Weise erreicht Haskell (eine funktionale Programmiersprache, die die funktionale Reinheit erzwingt) es.
Pharap
50

Die einfache Antwort auf Ihre Frage lautet, dass sie Math.random()gegen Regel 2 verstößt.

Viele andere Antworten hier haben darauf hingewiesen, dass das Vorhandensein von Math.random()bedeutet, dass diese Funktion nicht rein ist. Aber ich denke, es lohnt sich zu sagen, warum Math.random() Taints-Funktionen es verwenden.

Math.random()Beginnt wie alle Pseudozufallszahlengeneratoren mit einem "Startwert". Dieser Wert wird dann als Ausgangspunkt für eine Kette von Bitmanipulationen auf niedriger Ebene oder für andere Operationen verwendet, die zu einer unvorhersehbaren (aber nicht wirklich zufälligen ) Ausgabe führen.

In JavaScript ist der Prozess implementierungsabhängig, und im Gegensatz zu vielen anderen Sprachen bietet JavaScript keine Möglichkeit, den Startwert auszuwählen :

Die Implementierung wählt den anfänglichen Startwert für den Zufallszahlengenerierungsalgorithmus aus; Es kann vom Benutzer nicht ausgewählt oder zurückgesetzt werden.

Deshalb ist diese Funktion nicht rein: JavaScript verwendet im Wesentlichen einen impliziten Funktionsparameter, über den Sie keine Kontrolle haben. Es liest diesen Parameter aus Daten, die an anderer Stelle berechnet und gespeichert wurden, und verstößt daher gegen Regel 2 in Ihrer Definition.

Wenn Sie dies zu einer reinen Funktion machen möchten, können Sie einen der hier beschriebenen alternativen Zufallszahlengeneratoren verwenden . Nennen Sie diesen Generator seedable_random. Es nimmt einen Parameter (den Startwert) und gibt eine "Zufallszahl" zurück. Natürlich ist diese Zahl überhaupt nicht zufällig; es wird eindeutig durch den Samen bestimmt. Deshalb ist dies eine reine Funktion. Die Ausgabe von seedable_randomist nur "zufällig" in dem Sinne, dass es schwierig ist, die Ausgabe basierend auf der Eingabe vorherzusagen.

Die reine Version dieser Funktion müsste drei Parameter annehmen :

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

Für jedes gegebene Dreifach von (min, max, seed)Parametern wird immer das gleiche Ergebnis zurückgegeben.

Beachten Sie, dass Sie einen Weg finden müssen, um den Startwert zufällig seedable_randomzu bestimmen , wenn die Ausgabe von wirklich zufällig sein soll! Und jede Strategie, die Sie verwendet haben, wäre unweigerlich nicht rein, da Sie Informationen aus einer Quelle außerhalb Ihrer Funktion sammeln müssten. Wie mtraceur und jpmc26 mich erinnern, umfasst dies alle physikalischen Ansätze: Hardware-Zufallszahlengeneratoren , Webcams mit Objektivdeckeln , atmosphärische Geräuschkollektoren - sogar Lavalampen . All dies beinhaltet die Verwendung von Daten, die außerhalb der Funktion berechnet und gespeichert wurden.

senderle
quelle
8
Math.random () liest nicht nur seinen "Startwert", sondern ändert ihn auch, sodass der nächste Aufruf etwas anderes zurückgibt. Abhängig vom und Ändern des statischen Zustands ist dies definitiv schlecht für eine reine Funktion.
Nate Eldredge
2
@NateEldredge, ganz so! Das einfache Lesen eines implementierungsabhängigen Werts reicht jedoch aus, um die Reinheit zu beeinträchtigen. Haben Sie zum Beispiel jemals bemerkt, dass Python 3-Hashes zwischen Prozessen nicht stabil sind?
senderle
2
Wie würde sich diese Antwort ändern, wenn Math.randomkein PRNG verwendet würde, sondern stattdessen ein Hardware-RNG implementiert würde? Das Hardware-RNG hat nicht wirklich einen Zustand im normalen Sinne, aber es erzeugt zufällige Werte (und daher ist die Funktionsausgabe unabhängig von der Eingabe immer noch unterschiedlich), oder?
mtraceur
@mtraceur, das ist richtig. Aber ich denke nicht, dass sich die Antwort viel ändern würde. Aus diesem Grund verbringe ich in meiner Antwort keine Zeit damit, über "Zustand" zu sprechen. Lesen von einem Hardware-RNG bedeutet auch Lesen von "Daten, die an anderer Stelle berechnet und gespeichert wurden". Es ist nur so, dass die Daten berechnet und auf dem physischen Medium des Computers selbst gespeichert werden, während dieser mit seiner Umgebung interagiert.
senderle
1
Dieselbe Logik gilt auch für komplexere Randomisierungsschemata, selbst für solche wie das atmosphärische Rauschen von Random.org . +1
jpmc26
38

Eine reine Funktion ist eine Funktion, bei der der Rückgabewert nur durch seine Eingabewerte ohne beobachtbare Nebenwirkungen bestimmt wird

Mit Math.random bestimmen Sie den Wert durch etwas anderes als Eingabewerte. Es ist keine reine Funktion.

Quelle

TKoL
quelle
25

Nein, es ist keine reine Funktion, da ihre Ausgabe nicht nur von der bereitgestellten Eingabe abhängt (Math.random () kann einen beliebigen Wert ausgeben), während reine Funktionen immer denselben Wert für dieselben Eingaben ausgeben sollten.

Wenn eine Funktion rein ist, können Sie sicher mehrere Anrufe mit denselben Eingaben optimieren und nur das Ergebnis eines früheren Anrufs wiederverwenden.

PS Zumindest für mich und für viele andere hat Redux den Begriff reine Funktion populär gemacht. Direkt aus den Redux-Dokumenten :

Dinge, die Sie niemals in einem Reduzierer tun sollten:

  • Mutieren Sie seine Argumente;

  • Führen Sie Nebenwirkungen wie API-Aufrufe und Routing-Übergänge durch.

  • Rufen Sie nicht reine Funktionen auf, z. B. Date.now () oder Math.random ().

Shubhnik Singh
quelle
3
Zwar haben andere großartige Antworten geliefert, aber ich konnte mir nicht widersetzen, als mir Redux-Dokumente in den Sinn kamen und Math.random () speziell in ihnen erwähnt wurde :)
Shubhnik Singh
20

Aus mathematischer Sicht ist Ihre Unterschrift nicht

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

aber

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

wo das environmentin der Lage ist, Ergebnisse zu liefern Math.random(). Wenn Sie den Zufallswert tatsächlich generieren, wird die Umgebung als Nebeneffekt mutiert, sodass Sie auch eine neue Umgebung zurückgeben, die nicht der ersten entspricht!

Mit anderen Worten, wenn Sie irgendeine Art von Eingabe benötigen, die nicht aus anfänglichen Argumenten stammt (die <number, number> Teil) stammt, müssen Sie über eine Ausführungsumgebung verfügen (die in diesem Beispiel den Status für bereitstellt Math). Gleiches gilt für andere Dinge, die in anderen Antworten erwähnt werden, wie E / A oder ähnliches.


Als Analogie können Sie auch feststellen, dass objektorientierte Programmierung so dargestellt werden kann - wenn wir sagen, z

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

dann benutzen wir eigentlich

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

Das aufgerufene Objekt ist Teil der Umgebung. Und warum der SomeClassTeil des Ergebnisses? Weil sich auch somethingder Zustand hätte ändern können!

Adam Kotwasinski
quelle
7
Schlimmer noch, die Umgebung ist auch mutiert, so test: <environment, number, number> -> <environment, number>sollte es sein
Bergi
1
Ich bin mir nicht sicher, ob das OO-Beispiel sehr ähnlich ist. a.F(b, c)kann als syntaktischer Zucker für F(a, b, c)mit einer speziellen Regel angesehen werden, um überladene Definitionen Fbasierend auf dem Typ von zu versenden a(so repräsentiert Python es tatsächlich). Ist aaber in beiden Notationen immer noch explizit, während die Umgebung in einer nicht reinen Funktion im Quellcode nie erwähnt wird.
IMSoP
11

Reine Funktionen geben immer den gleichen Wert für den gleichen Eingang zurück. Reine Funktionen sind vorhersehbar und referenziell transparent, was bedeutet, dass wir den Funktionsaufruf durch die zurückgegebene Ausgabe ersetzen können und die Funktionsweise des Programms nicht verändert wird.

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

Rishabh Mishra
quelle
10

Zusätzlich zu den anderen Antworten, die korrekt darauf hinweisen, dass diese Funktion nicht deterministisch ist, hat sie auch einen Nebeneffekt: Sie führt dazu, dass zukünftige Anrufe math.random()eine andere Antwort zurückgeben. Und ein Zufallszahlengenerator, der diese Eigenschaft nicht besitzt, führt im Allgemeinen eine Art E / A aus, z. B. zum Lesen von einem vom Betriebssystem bereitgestellten Zufallsgerät. Beides ist für eine reine Funktion verboten.

Davislor
quelle
7

Nein, ist es nicht. Sie können das Ergebnis überhaupt nicht herausfinden, daher kann dieser Code nicht getestet werden. Um diesen Code testbar zu machen, müssen Sie die Komponente extrahieren, die die Zufallszahl generiert:

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

Jetzt können Sie den Generator verspotten und Ihren Code richtig testen:

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

Und in Ihrem "Produktions" -Code:

const result = test(1, 2, Math.random);
Tyrannisieren
quelle
1
▲ für Ihren Gedanken zur Testbarkeit. Mit ein wenig Sorgfalt können Sie auch wiederholbare Tests erstellen, während Sie a akzeptieren util.Random, die Sie zu Beginn eines Testlaufs festlegen können, um altes Verhalten zu wiederholen, oder für einen neuen (aber wiederholbaren) Lauf. Bei Multithreading können Sie dies möglicherweise im Hauptthread tun und Randomdamit wiederholbare threadlokale Randoms setzen. Soweit ich es verstehe, test(int,int,Random)wird es jedoch nicht als rein angesehen, da es den Zustand des Random.
PJTraill
2

Würden Sie mit folgendem in Ordnung sein:

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

gleichwertig sein mit

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

?

Sie sehen, die Definition von pure ist eine Funktion, deren Ausgabe sich nur durch ihre Eingaben ändert. Wenn wir sagen, dass JavaScript eine Möglichkeit hat, eine Funktion rein zu kennzeichnen und dies zu nutzen, kann der Optimierer den ersten Ausdruck als zweiten umschreiben.

Ich habe praktische Erfahrung damit. SQL Server erlaubt getdate()und newid()in "reinen" Funktionen und der Optimierer würde Aufrufe nach Belieben deduplizieren. Manchmal tat dies etwas Dummes.

Joshua
quelle