Чи є функцією, яка називає Math.random () чистою?


112

Чи є наступна чиста функція?

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

Я розумію, що чиста функція відповідає цим умовам:

  1. Він повертає значення, обчислене з параметрів
  2. Це не робить ніякої іншої роботи, крім обчислення значення повернення

Якщо це визначення правильне, чи моя функція є чистою функцією? Або моє розуміння того, що визначає чисту функцію, є неправильним?


66
"Це не робить ніякої роботи, крім обчислення значення повернення", але він називає, Math.random()що змінює стан RNG.
Пол Дрейпер

1
Другий момент більше нагадує "він не змінює зовнішній (до функції) стан"; і перше слід доповнити якось так, як "воно повертає значення SAME, обчислене з параметрів SAME", як
писали

Чи існує поняття напівчистої функції, що допускає випадковість? Наприклад, test(a,b)завжди повертається один і той же об'єкт Random(a,b)(який може представляти різні конкретні числа)? Якщо ви зберігаєте Randomсимволічну, то вона є чистою в класичному розумінні, якщо оцінювати її рано і вводити цифри, можливо, як своєрідна оптимізація, функція все одно зберігає певну «чистоту».
jdm

1
"Кожен, хто розглядає арифметичні методи отримання випадкових цифр, перебуває, звичайно, у стані гріха". - Джон фон Нойман
Стів Куо

1
@jdm, якщо ви слідуєте за темою "напівчисті", де ви вважаєте функції чистими модулями деякими чітко визначеними побічними ефектами, ви можете винайти монади. Ласкаво просимо в темну сторону. > :)
luqui

Відповіді:


185

Ні це не так. За умови однакового введення ця функція повертає різні значення. І тоді ви не можете побудувати "таблицю", яка відображає вхід і виходи.

З статті Вікіпедії для функції «Чиста» :

Функція завжди оцінює одне і те ж значення результату, задаючи однакові значення аргументів. Значення результату функції не може залежати від будь-якої прихованої інформації або стану, яка може змінюватися під час виконання програми або між різними виконанням програми, а також не може залежати від зовнішнього вводу пристроїв вводу / виводу

Крім того, інша річ полягає в тому, що чисту функцію можна замінити таблицею, яка представляє відображення з введення та виводу, як це пояснено в цій темі .

Якщо ви хочете переписати цю функцію та змінити її на чисту функцію, вам також слід передавати випадкове значення як аргумент

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

а потім назвіть це так (наприклад, з 2 і 5 як min і max):

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

2
Що робити, якщо перед повторним викликом ви повторно засівали випадковий генератор всередині функції Math.random?
cs95

16
@ cᴏʟᴅsᴘᴇᴇᴅ Навіть тоді це все ще матиме побічні ефекти (зміна майбутнього Math.randomрезультату); щоб воно було чистим, вам доведеться якось зберегти поточний стан RNG, перезавантажити його, викликати Math.randomта відновити його до попереднього стану.
LegionMammal978

2
@ cᴏʟᴅsᴘᴇᴇᴅ Всі обчислені RNG засновані на фальшивій випадковості. Щось повинно працювати під цим, що призводить до появи випадкових випадків, і ви не можете це пояснити, роблячи це нечистим. Крім того, і, мабуть, важливіше ваше питання, ви не можете посіяти Math.random
zfrisch

14
@ LegionMammal978… і зробіть це атомно.
wchargin

2
@ cᴏʟᴅsᴘᴇᴇᴅ Існують способи створення RNG, які працюють з чистими функціями, але це включає передачу стану RNG у функцію та повернення функції заміщення RNG, саме так досягає Haskell (функціональна мова програмування, що забезпечує функціональну чистоту). це.
Фарап

50

Проста відповідь на ваше запитання полягає в тому, що Math.random()порушує правило №2.

Багато інших відповідей тут вказували на те, що наявність засобів Math.random()означає, що ця функція не є чистою. Але я вважаю, що варто сказати, чому Math.random() заплямовує функції, які його використовують.

Як і всі генератори псевдовипадкових чисел, Math.random()починається зі значення «насіння». Потім воно використовує це значення як вихідну точку для ланцюжка бітових маніпуляцій низького рівня або інших операцій, які призводять до непередбачуваного (але насправді не випадкового ) виводу.

У JavaScript процес, що займається цим процесом, залежить від реалізації, і на відміну від багатьох інших мов, JavaScript не пропонує способу вибору насіння :

Реалізація вибирає початкове насіння в алгоритм генерації випадкових чисел; користувач не може його вибрати або скинути.

Ось чому ця функція не є чистою: JavaScript по суті використовує неявний функціональний параметр, над яким ви не маєте контролю. Він зчитує цей параметр із даних, обчислених та збережених в іншому місці, а тому порушує правило №2 у вашому визначенні.

Якщо ви хочете зробити це чистою функцією, ви можете використовувати один з альтернативних генераторів випадкових чисел, описаних тут . Викличте цей генератор seedable_random. Він бере один параметр (насіння) і повертає "випадкове" число. Звичайно, це число зовсім не є випадковим; це однозначно визначається насінням. Ось чому це чиста функція. Вихід " seedable_randomлише" випадковий "у тому сенсі, що передбачити вихід на основі введення важко.

У чистому варіанті цієї функції потрібно взяти три параметри:

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

Для будь-якого даного трійки (min, max, seed)параметрів це завжди поверне той самий результат.

Зауважте, що якщо ви хотіли, щоб результат seedable_randomбув справді випадковим, вам знадобиться знайти спосіб рандомізації насіння! І яка б стратегія ви не використовували, вона неминуче була б нечистою, оскільки вона вимагатиме від вас збору інформації з джерела, що не відповідає вашій функції. Як нагадують mtraceur та jpmc26 , це включає всі фізичні підходи: апаратні генератори випадкових чисел , веб-камери з кришками об'єктивів , атмосферні колектори шуму - навіть лавові лампи . Усе це включає використання даних, обчислених та збережених поза функцією.


8
Math.random () не тільки читає своє "насіння", але і модифікує його, щоб наступний дзвінок повернув щось інше. Залежно від та видозмінюваних, статичний стан, безумовно, поганий для чистої функції.
Нейт Елдредж

2
@NateEldredge, зовсім так! Хоча для читання чистоти достатньо просто прочитати значення, що залежить від реалізації. Наприклад, коли-небудь помічайте, як хеші Python 3 не стійкі між процесами?
senderle

2
Як змінилася б ця відповідь, якщо Math.randomвона не використовувала PRNG, а була натомість реалізована за допомогою апаратного RNG? Апаратний RNG насправді не має стану в нормальному розумінні, але він видає випадкові значення (і, таким чином, вихідний показник функцій все ще відрізняється незалежно від введення), правда?
mtraceur

@mtraceur, це правильно. Але я не думаю, що відповідь сильно зміниться. Насправді, саме тому я не витрачаю часу на розмову про "стан" у своїй відповіді. Читання з апаратного RNG також означає читання з "даних, обчислених і збережених в іншому місці". Просто дані обчислюються та зберігаються у фізичному середовищі самого комп’ютера під час взаємодії з його середовищем.
senderle

1
Ця ж логіка застосовується навіть до більш складних схем рандомізації, навіть таких як атмосферний шум Random.org . +1
jpmc26

38

Чиста функція - це функція, де повернене значення визначається лише його вхідними значеннями, без помітних побічних ефектів

Використовуючи Math.random, ви визначаєте його значення чимось, крім вхідних значень. Це не чиста функція.

джерело


25

Ні, це не чиста функція, оскільки її вихід не залежить тільки від наданого входу (Math.random () може виводити будь-яке значення), тоді як чисті функції завжди повинні виводити одне і те ж значення для одних і тих же входів.

Якщо функція чиста, безпечно оптимізувати декілька викликів з однаковими входами та просто повторно використовувати результат попереднього дзвінка.

PS для мене принаймні і для багатьох інших, redux зробив термін чистою функцією популярним. Прямо з редукційних документів :

Речі, які ніколи не повинні робити всередині редуктора:

  • Змініть свої аргументи;

  • Виконувати такі побічні ефекти, як виклики API та маршрутизація переходів;

  • Викликайте нечисті функції, наприклад Date.now () або Math.random ().


3
Хоча інші дали чудові відповіді, але я не міг протистояти собі, коли до мене прийшов думка домен-редуксів, а Math.random () спеціально згаданий у них :)
Shubhnik Singh

20

З математичної точки зору, ваш підпис - ні

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

але

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

де environmentздатний забезпечити результати Math.random(). І фактично генерування випадкової величини мутує середовище як побічний ефект, тому ви також повертаєте нове середовище, яке не дорівнює першому!

Іншими словами, якщо вам потрібен будь-який вхід, який не виходить з початкових аргументів ( <number, number>частини), то вам потрібно забезпечити середовище виконання (яке в цьому прикладі забезпечує стан Math). Те саме стосується інших речей, згаданих іншими відповідями, наприклад, вводу / виводу або подібного.


Як аналогія, ви також можете помітити, як це може бути представлено об'єктно-орієнтоване програмування - якщо ми скажемо, наприклад

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

то насправді ми використовуємо

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

з об'єктом, який має свій метод, викликається частиною оточення. І чому SomeClassчастина результату? Тому somethingщо держава могла змінитися і так!


7
Що ще гірше, навколишнє середовище також мутується, тому test: <environment, number, number> -> <environment, number>воно повинно бути
Бергі

1
Я не впевнений, що приклад ОО дуже схожий. a.F(b, c)може розглядатися як синтаксичний цукор для, який F(a, b, c)має спеціальне правило для відправлення перевантажених визначень на Fоснові типу a(це насправді так представляє Python). Але aвсе ще явна в обох позначеннях, тоді як середовище в нечистій функції ніколи не згадується у вихідному коді.
IMSoP

11

Чисті функції завжди повертають одне і те ж значення для одного входу. Чисті функції є передбачуваними та прозорими, це означає, що ми можемо замінити виклик функції поверненим результатом, і це не змінить роботу програми.

https://github.com/MostlyAdeffic/mostly-adeffic-guide/blob/master/ch3.md


10

Окрім інших відповідей, які правильно вказують на те, як ця функція є недетермінованою, вона має і побічний ефект: вона спричинить майбутні дзвінки, math.random()щоб повернути іншу відповідь. А генератор випадкових чисел, що не має цього властивості, як правило, виконує якийсь ввід / вивід, наприклад, зчитування з випадкового пристрою, що надається ОС. Або є багатослівним для чистої функції.


7

Ні, це не так. Ви взагалі не можете з'ясувати результат, тому цей фрагмент коду не можна перевірити. Щоб зробити цей код перевіряючим, потрібно витягти компонент, що генерує випадкове число:

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

Тепер ви можете знущатися над генератором і правильно перевіряти код:

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

А у вашому "виробничому" коді:

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

1
▲ за вашу думку про доказовість. З невеликою обережністю ви також можете створити повторювані тести під час прийняття util.Random, які ви можете використати на початку тестового циклу для повторення старої поведінки або для нового (але повторюваного) запуску. Якщо це багаторядне нанизування, можливо, ви зможете зробити це в основній нитці і використовувати це Randomдля нанесення повторюваних локальних ниток Randoms. Однак, як я розумію, test(int,int,Random)це не вважається чистим, оскільки воно змінює стан Random.
PJTraill

2

Невже ви будете добре:

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

бути рівнозначним

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

?

Розумієте, визначення pure є функцією, вихід якої не змінюється нічим іншим, крім її входів. Якщо ми скажемо, що в JavaScript був спосіб позначати функцію чистою і скористатися цим, оптимізатору було б дозволено переписати перший вираз як другий.

Я маю з цим практичний досвід. SQL-сервер дозволений getdate()і newid()в "чистих" функціях, і оптимізатор може проводити дзвінки за бажанням. Іноді це робило б щось німе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.