Является ли функция, вызывающая Math.random () чистой?

112

Это чистая функция?

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

Насколько я понимаю, чистая функция подчиняется этим условиям:

  1. Он возвращает значение, вычисленное из параметров
  2. Он не выполняет никакой работы, кроме вычисления возвращаемого значения.

Если это определение верно, является ли моя функция чистой функцией? Или я неправильно понимаю, что определяет чистую функцию?

Киви рупела
источник
66
«Он не выполняет никакой работы, кроме вычисления возвращаемого значения», но вызывает, Math.random()который изменяет состояние ГСЧ.
Пол Дрейпер
1
Второй пункт больше похож на «он не меняет внешнее (по отношению к функции) состояние»; и первый должен быть дополнен чем-то вроде «он возвращает ТО ЖЕ значение, вычисленное из ОДИНАКОВЫХ параметров», как
написано
Есть ли понятие получистой функции с учетом случайности? Например, test(a,b)всегда возвращает один и тот же объект Random(a,b)(который может представлять разные конкретные числа)? Если вы сохраняете Randomсимволический характер, он чист в классическом смысле, если вы оцениваете его заранее и вводите числа, возможно, как своего рода оптимизация, функция все еще сохраняет некоторую «чистоту».
JDM
1
«Всякий, кто рассматривает арифметические методы получения случайных чисел, конечно, находится в состоянии греха». - Джон фон Нойман
Стив Куо
1
@jdm, если вы будете следовать потоку "полу-чистого", где вы рассматриваете функции как чистые по модулю некоторых четко определенных побочных эффектов, вы можете в конечном итоге изобрести монады. Добро пожаловать на темную сторону. > :)
luqui 03

Ответы:

185

Нет, это не так. При одном и том же вводе эта функция будет возвращать разные значения. И тогда вы не сможете построить «таблицу», которая сопоставляет входные и выходные данные.

Из статьи в Википедии о функции Pure :

Функция всегда оценивает одно и то же значение результата при одном и том же значении аргумента. Значение результата функции не может зависеть от какой-либо скрытой информации или состояния, которое может измениться во время выполнения программы или между различными выполнениями программы, а также не может зависеть от какого-либо внешнего ввода от устройств ввода-вывода.

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

Если вы хотите переписать эту функцию и изменить ее на чистую функцию, вы также должны передать случайное значение в качестве аргумента.

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ᴘᴇᴇᴅ Все вычисленные ГСЧ основаны на имитации случайности. Что-то должно работать внизу, что заставляет его казаться случайным, и вы не можете это объяснить, делая его нечистым. Кроме того, что, вероятно, более важно для вашего вопроса, вы не можете запустить Math.random
zfrisch
14
@ LegionMammal978… и делайте это атомарно.
wchargin
2
@ cᴏʟᴅsᴘᴇᴇᴅ Существуют способы иметь ГСЧ, которые действительно работают с чистыми функциями, но это включает в себя передачу состояния ГСЧ функции и получение функцией возврата замещающего состояния ГСЧ, что является тем, как Haskell (язык функционального программирования, обеспечивающий функциональную чистоту) достигает Это.
Pharap
51

Простой ответ на ваш вопрос - это 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 , сюда входят все физические подходы: аппаратные генераторы случайных чисел , веб-камеры с крышками линз , коллекторы атмосферного шума - даже лавовые лампы . Все это связано с использованием данных, рассчитанных и сохраненных вне функции.

senderle
источник
8
Math.random () не только читает свое «семя», но и изменяет его, так что следующий вызов вернет что-то другое. В зависимости от и изменения статическое состояние определенно плохо для чистой функции.
Нейт Элдридж
2
@NateEldredge, именно так! Хотя простого чтения значения, зависящего от реализации, достаточно, чтобы нарушить чистоту. Например, вы когда-нибудь замечали, что хэши Python 3 нестабильны между процессами?
senderle
2
Как бы изменился этот ответ, если Math.randomбы не использовался ГПСЧ, а вместо этого был реализован аппаратный ГСЧ? Аппаратный ГСЧ на самом деле не имеет состояния в обычном смысле, но он производит случайные значения (и, таким образом, вывод функции по-прежнему отличается независимо от ввода), верно?
mtraceur
@mtraceur, правильно. Но я не думаю, что ответ сильно изменится. Собственно, именно поэтому я не трачу время на «состояние» в своем ответе. Чтение из аппаратного ГСЧ также означает чтение из «данных, рассчитанных и сохраненных в другом месте». Просто данные рассчитываются и хранятся на физическом носителе самого компьютера, когда он взаимодействует со своей средой.
senderle 01
1
Эта же логика применима даже к более сложным схемам рандомизации, даже к таким, как атмосферный шум Random.org . +1
jpmc26 01
39

Чистая функция - это функция, в которой возвращаемое значение определяется только входными значениями без наблюдаемых побочных эффектов.

Используя Math.random, вы определяете его значение не по входным значениям. Это не чистая функция.

источник

ТКол
источник
25

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

Если функция является чистой, можно безопасно оптимизировать несколько вызовов с одними и теми же входными данными и просто повторно использовать результат более раннего вызова.

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

Что никогда не следует делать внутри редуктора:

  • Изменяйте его аргументы;

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

  • Вызов нечистых функций, например Date.now () или Math.random ().

Шубник Сингх
источник
3
Хотя другие предоставили отличные ответы, но я не мог устоять перед собой, когда мне в голову пришли redux doc и Math.random () специально упомянул в них :)
Шубник Сингх
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
Я не уверен, что пример OO очень похож. a.F(b, c)можно рассматривать как синтаксический сахар для F(a, b, c)со специальным правилом для отправки в перегруженные определения на Fоснове типа a(на самом деле Python представляет его именно так). Но aпо-прежнему является явным в обеих нотациях, тогда как среда в нечистой функции никогда не упоминается в исходном коде.
IMSoP 02
11

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

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

Ришаб Мишра
источник
10

В дополнение к другим ответам, которые правильно указывают на то, что эта функция недетерминирована, у нее также есть побочный эффект: он заставит будущие вызовы math.random()возвращать другой ответ. А генератор случайных чисел, у которого нет этого свойства, обычно выполняет какой-то ввод-вывод, например, чтение со случайного устройства, предоставляемого ОС. Либо запрещено для чистой функции.

Davislor
источник
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
▲ за вашу мысль о проверяемости. С небольшой осторожностью вы также можете создавать повторяющиеся тесты, принимая a util.Random, который вы можете заполнить в начале тестового прогона для повторения старого поведения или для нового (но повторяемого) прогона. При многопоточности вы можете сделать это в основном потоке и использовать это Randomдля заполнения повторяемых локальных потоков Random. Однако, насколько я понимаю, test(int,int,Random)это не считается чистым, так как это изменяет состояние файла Random.
PJTraill 01
2

Вы бы согласились со следующим:

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

быть эквивалентным

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

?

Видите ли, определение pure - это функция, вывод которой не изменяется ни на что, кроме его входов. Если мы скажем, что в JavaScript есть способ пометить функцию как чистую и воспользоваться этим, оптимизатору будет разрешено переписать первое выражение как второе.

У меня есть практический опыт в этом. SQL-сервер разрешен getdate()и newid()в «чистых» функциях, и оптимизатор будет выводить вызовы по своему желанию. Иногда это было бы глупо.

Джошуа
источник