Czy funkcja, która wywołuje Math.random () jest czysta?

112

Czy poniższe czynności są czystą funkcją?

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

Rozumiem, że czysta funkcja spełnia następujące warunki:

  1. Zwraca wartość obliczoną na podstawie parametrów
  2. Nie wykonuje żadnej pracy poza obliczaniem wartości zwracanej

Jeśli ta definicja jest poprawna, czy moja funkcja jest funkcją czystą? A może moje rozumienie definicji czystej funkcji jest nieprawidłowe?

Kiwi Rupela
źródło
66
„Nie wykonuje żadnej pracy poza obliczaniem wartości zwracanej”, ale wywołuje, Math.random()co zmienia stan RNG.
Paul Draper
1
Drugi punkt jest bardziej podobny do „nie zmienia stanu zewnętrznego (względem funkcji)”; a pierwszy powinien być uzupełniony czymś w rodzaju "zwraca
TA SAMĄ
Czy istnieje pojęcie funkcji półprzewodnikowej, dopuszczającej losowość? Np. test(a,b)Zawsze zwraca ten sam obiekt Random(a,b)(który może reprezentować różne konkretne liczby)? Jeśli zachowasz Randomsymbolikę, jest ona czysta w klasycznym sensie, jeśli ocenisz ją wcześnie i wpiszesz w liczby, być może w ramach optymalizacji, funkcja nadal zachowuje pewną „czystość”.
jdm
1
„Każdy, kto rozważa arytmetyczne metody tworzenia losowych cyfr, jest oczywiście w stanie grzechu”. - John von Neumann
Steve Kuo
1
@jdm, jeśli podążasz za wątkiem "pół-czystym", w którym uważasz, że funkcje czyste modulo mają dobrze zdefiniowane efekty uboczne, możesz w końcu wymyślić monady. Witamy na ciemnej stronie. > :)
luqui

Odpowiedzi:

185

Nie, nie jest. Przy tych samych danych wejściowych ta funkcja zwróci różne wartości. A potem nie możesz zbudować „tabeli”, która odwzorowuje wejście i wyjście.

Z artykułu na Wikipedii dotyczącego funkcji Pure :

Funkcja zawsze oblicza tę samą wartość wyniku przy tych samych wartościach argumentów. Wartość wynikowa funkcji nie może zależeć od żadnych ukrytych informacji lub stanu, który może ulec zmianie podczas wykonywania programu lub między różnymi wykonaniami programu, ani też nie może zależeć od jakichkolwiek zewnętrznych danych wejściowych z urządzeń we / wy

Inną rzeczą jest to, że czystą funkcję można zastąpić tabelą, która reprezentuje mapowanie z wejścia i wyjścia, jak wyjaśniono w tym wątku .

Jeśli chcesz przepisać tę funkcję i zmienić ją na czystą funkcję, powinieneś również przekazać wartość losową jako argument

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

a następnie nazwij to w ten sposób (na przykład z 2 i 5 jako min i max):

test( Math.random(), 2, 5)
Christian Benseler
źródło
2
Co by było, gdybyś za każdym razem przed wywołaniem ponownie zaszczepił generator losowy wewnątrz funkcji Math.random?
cs95
16
@ cᴏʟᴅsᴘᴇᴇᴅ Nawet wtedy nadal miałoby to skutki uboczne (zmiana przyszłej Math.randomprodukcji); aby był czysty, musiałbyś w jakiś sposób zapisać bieżący stan RNG, ponownie go ustawić, wywołać Math.randomi przywrócić do poprzedniego stanu.
LegionMammal978
2
@ cᴏʟᴅsᴘᴇᴇᴅ Wszystkie obliczone liczby losowe są oparte na udawaniu losowości. Coś musi biegać pod spodem, co powoduje, że wydaje się to przypadkowe i nie możesz tego wyjaśnić, czyniąc go nieczystym. Ponadto, i prawdopodobnie ważniejsze dla twojego pytania, nie możesz zasiać
Math.random
14
@ LegionMammal978… i zrób to atomowo.
wchargin
2
@ cᴏʟᴅsᴘᴇᴇᴅ Istnieją sposoby na posiadanie RNG, które działają z czystymi funkcjami, ale wymaga to przekazania stanu RNG do funkcji i zwrócenia przez funkcję stanu zastępczego RNG, w ten sposób osiąga Haskell (funkcjonalny język programowania, który wymusza czystość funkcjonalną) to.
Pharap
50

Prosta odpowiedź na Twoje pytanie brzmi: Math.random()narusza zasadę nr 2.

Wiele innych odpowiedzi tutaj wskazywało, że obecność Math.random()oznacza, że ​​ta funkcja nie jest czysta. Ale myślę, że warto powiedzieć, dlaczego Math.random() skazi funkcje, które go używają.

Podobnie jak wszystkie generatory liczb pseudolosowych, Math.random()zaczyna się od wartości „ziarna”. Następnie używa tej wartości jako punktu wyjścia dla łańcucha niskopoziomowych manipulacji bitami lub innych operacji, które powodują nieprzewidywalne (ale nie w rzeczywistości losowe ) dane wyjściowe.

W JavaScript zaangażowany proces jest zależny od implementacji i w przeciwieństwie do wielu innych języków JavaScript nie zapewnia możliwości wyboru ziarna :

Implementacja wybiera początkowe ziarno do algorytmu generowania liczb losowych; nie może być wybrany ani zresetowany przez użytkownika.

Dlatego ta funkcja nie jest czysta: JavaScript zasadniczo używa niejawnego parametru funkcji, nad którym nie masz kontroli. Odczytuje ten parametr z danych obliczonych i przechowywanych w innym miejscu, a zatem narusza zasadę nr 2 w Twojej definicji.

Jeśli chcesz, aby była to czysta funkcja, możesz użyć jednego z alternatywnych generatorów liczb losowych opisanych tutaj . Zadzwoń do tego generatora seedable_random. Pobiera jeden parametr (ziarno) i zwraca „losową” liczbę. Oczywiście ta liczba wcale nie jest przypadkowa; jest wyjątkowo określona przez nasienie. Dlatego jest to czysta funkcja. Wynik seedable_randomjest tylko „losowy” w tym sensie, że przewidywanie wyniku na podstawie danych wejściowych jest trudne.

Czysta wersja tej funkcji musiałaby mieć trzy parametry:

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

Dla dowolnej potrójnej (min, max, seed)wartości zawsze zwróci to ten sam wynik.

Zwróć uwagę, że jeśli chcesz, aby wynik seedable_randombył naprawdę losowy, musisz znaleźć sposób na losowanie ziarna! I jakakolwiek strategia, której użyłeś, nieuchronnie nie byłaby czysta, ponieważ wymagałaby od ciebie zebrania informacji ze źródła spoza twojej funkcji. Jak przypominają mi mtraceur i jpmc26 , obejmuje to wszystkie podejścia fizyczne: sprzętowe generatory liczb losowych , kamery internetowe z osłonami obiektywów , kolektory szumów atmosferycznych - nawet lampy lawowe . Wszystko to wiąże się z wykorzystaniem danych obliczonych i przechowywanych poza funkcją.

nadawca
źródło
8
Math.random () nie tylko czyta swoje „ziarno”, ale także modyfikuje je, tak aby następne wywołanie zwróciło coś innego. W zależności od i modyfikującego stan statyczny jest zdecydowanie zły dla czystej funkcji.
Nate Eldredge
2
@NateEldredge, całkiem tak! Chociaż samo odczytanie wartości zależnej od implementacji wystarczy, aby złamać czystość. Na przykład, czy kiedykolwiek zauważyłeś, że skróty Pythona 3 nie są stabilne między procesami?
senderle
2
Jak zmieniłaby się ta odpowiedź, gdyby Math.randomnie używała PRNG, ale zamiast tego została zaimplementowana przy użyciu sprzętowego RNG? Sprzętowy RNG tak naprawdę nie ma stanu w normalnym sensie, ale generuje wartości losowe (a zatem wyjście funkcji jest nadal różne niezależnie od wejścia), prawda?
mtraceur
@mtraceur, zgadza się. Ale nie sądzę, żeby odpowiedź wiele zmieniła. Właściwie to właśnie dlatego nie spędzam czasu na mówieniu o „stanie” w mojej odpowiedzi. Odczyt ze sprzętowego RNG oznacza również odczyt „danych obliczonych i przechowywanych w innym miejscu”. Chodzi o to, że dane są obliczane i przechowywane na fizycznym nośniku samego komputera, gdy oddziałuje on ze swoim otoczeniem.
senderle
1
Ta sama logika odnosi się nawet do bardziej wyrafinowanych schematów randomizacji, nawet takich jak szum atmosferyczny Random.org . +1
jpmc26
38

Czysta funkcja to funkcja, w której wartość zwracana jest określana tylko przez jej wartości wejściowe, bez obserwowalnych skutków ubocznych

Używając Math.random, określasz jego wartość na podstawie czegoś innego niż wartości wejściowe. To nie jest czysta funkcja.

źródło

TKoL
źródło
25

Nie, to nie jest czysta funkcja, ponieważ jej wyjście nie zależy tylko od podanego wejścia (Math.random () może wyprowadzić dowolną wartość), podczas gdy czyste funkcje powinny zawsze zwracać tę samą wartość dla tych samych danych wejściowych.

Jeśli funkcja jest czysta, można bezpiecznie zoptymalizować wiele wywołań z tymi samymi danymi wejściowymi i po prostu ponownie wykorzystać wynik wcześniejszego wywołania.

PS, przynajmniej dla mnie i dla wielu innych, dzięki redukcjix popularność określenia czysta funkcja stała się popularna. Prosto z dokumentów Redux :

Rzeczy, których nigdy nie powinieneś robić w reduktorze:

  • Zmutuj jego argumenty;

  • Wykonywanie efektów ubocznych, takich jak wywołania API i przejścia routingu;

  • Wywołaj nieczyste funkcje, np. Date.now () lub Math.random ().

Shubhnik Singh
źródło
3
Chociaż inni udzielili świetnych odpowiedzi, ale nie mogłem się oprzeć, gdy przyszedł mi do głowy redx doc i konkretnie wspomniał w nich
Math.random
20

Z matematycznego punktu widzenia Twój podpis nie jest

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

ale

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

gdzie environmentjest w stanie dostarczyć wyniki Math.random(). Generowanie wartości losowej powoduje mutację środowiska jako efekt uboczny, więc zwracasz także nowe środowisko, które nie jest równe pierwszemu!

Innymi słowy, jeśli potrzebujesz jakichkolwiek danych wejściowych, które nie pochodzą z argumentów początkowych ( <number, number>część), to musisz mieć zapewnione środowisko wykonawcze (które w tym przykładzie zapewnia stan dla Math). To samo dotyczy innych rzeczy wymienionych w innych odpowiedziach, takich jak I / O lub takie.


Analogicznie można zauważyć, że w ten sposób można przedstawić programowanie obiektowe - jeśli powiemy np

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

to faktycznie używamy

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

z obiektem, którego metoda została wywołana, będąc częścią środowiska. A dlaczego SomeClassczęść wyniku? Ponieważ somethingstan również mógł się zmienić!

Adam Kotwasiński
źródło
7
Co gorsza, środowisko również jest zmutowane, więc test: <environment, number, number> -> <environment, number>powinno być
Bergi
1
Nie jestem pewien, czy przykład OO jest bardzo podobny. a.F(b, c)może być postrzegany jako cukier syntaktyczny F(a, b, c)ze specjalną regułą do wysłania do przeciążonych definicji w Foparciu o typ a(tak właściwie przedstawia to Python). Ale anadal jest to wyraźne w obu notacjach, podczas gdy środowisko w funkcji innej niż czysta nigdy nie jest wspomniane w kodzie źródłowym.
IMSoP
10

Oprócz innych odpowiedzi, które poprawnie wskazują, jak ta funkcja jest niedeterministyczna, ma ona również efekt uboczny: spowoduje, że przyszłe wywołania funkcji math.random()zwrócą inną odpowiedź. A generator liczb losowych, który nie ma tej właściwości, generalnie wykonuje pewnego rodzaju operacje we / wy, na przykład odczytuje z losowego urządzenia dostarczonego przez system operacyjny. Albo jest verboten dla czystej funkcji.

Davislor
źródło
7

Nie, nie jest. W ogóle nie możesz znaleźć wyniku, więc tego fragmentu kodu nie można przetestować. Aby kod był testowalny, musisz wyodrębnić komponent, który generuje liczbę losową:

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

Teraz możesz mockować generator i poprawnie przetestować kod:

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

A w kodzie „produkcyjnym”:

const result = test(1, 2, Math.random);
Zabijaka
źródło
1
▲ za myśl o sprawdzalności. Przy odrobinie ostrożności można również wykonać powtarzalne testy, akceptując test util.Random, który można zainicjować na początku przebiegu testowego, aby powtórzyć stare zachowanie lub dla nowego (ale powtarzalnego) przebiegu. Jeśli korzystasz z wielu wątków, możesz to zrobić w głównym wątku i użyć tego Randomdo zainicjowania powtarzalnych lokalnych wątków Random. Jednak, jak rozumiem, test(int,int,Random)nie jest uważany za czysty, ponieważ zmienia stan Random.
PJTraill
2

Czy byłbyś w porządku z następującymi:

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

być równoważne z

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

?

Widzisz, definicja pure to funkcja, której wyjście nie zmienia się z niczym innym niż danymi wejściowymi. Gdybyśmy powiedzieli, że JavaScript miał sposób, aby oznaczyć funkcję jako czystą i skorzystać z tego, optymalizator mógłby przepisać pierwsze wyrażenie jako drugie.

Mam z tym praktyczne doświadczenie. Serwer SQL wolno getdate()i newid()funkcje „czystych” i optymalizator będzie DeDupe połączeń do woli. Czasami robiłoby to coś głupiego.

Joshua
źródło