Это продолжение моей предыдущей question
Тревис Браун указал, что java.util.Random является побочным эффектом и предложил случайную монаду Rng , чтобы сделать код чисто функциональным. Теперь я пытаюсь создать упрощенную случайную монаду, чтобы понять, как она работает.
Имеет ли смысл? Как вы могли бы исправить/улучшить объяснение ниже?
Случайный генератор
Сначала мы плагируем случайную производящую функцию из java.util.Random
// do some bit magic to generate a new random "seed" from the given "seed"
// and return both the new "seed" and a random value based on it
def next(seed: Long, bits: Int): (Long, Int) = ...
Обратите внимание, что next возвращает как новое семя, так и значение, а не только значение. Нам нужно это, чтобы передать новое семя другому вызову функции.
Случайная точка
Теперь напишите функцию для генерации случайной точки в единичном квадрате.
Предположим, что у нас есть функция для генерации случайного двойника в диапазоне [0, 1]
def randomDouble(seed: Long): (Long, Double) = ... // some bit magic
Теперь мы можем написать функцию для генерации случайной точки.
def randomPoint(seed: Long): (Long, (Double, Double)) = {
val (seed1, x) = randomDouble(seed)
val (seed2, y) = randomDouble(seed1)
(seed2, (x, y))
}
До сих пор такие хорошие и оба randomDouble и randomPoint чисты. Единственная проблема заключается в том, что мы создаем randomDouble для сборки randomPoint ad hoc. У нас нет общего инструмента для создания функций, дающих случайные значения.
Monad Random
Теперь мы определим общий инструмент для создания функций, дающих случайные значения. Во-первых, мы обобщаем тип randomDouble:
type Random[A] = Long => (Long, A) // generate a random value of type A
а затем создайте вокруг него класс-оболочку.
class Random[A](run: Long => (Long, A))
Нам нужна оболочка для определения методов flatMap (как bind в Haskell) и map, используемых для понимания.
class Random[A](run: Long => (Long, A)) {
def apply(seed: Long) = run(seed)
def flatMap[B](f: A => Random[B]): Random[B] =
new Random({seed: Long => val (seed1, a) = run(seed); f(a)(seed1)})
def map[B](f: A => B): Random[B] =
new Random({seed: Long = val (seed1, a) = run(seed); (seed1, f(a))})
}
Теперь мы добавляем factory -функцию для создания тривиального Random[A] (который, кстати, является абсолютно детерминированным, а не "случайным" ). Это функция возврата (как возвращение в Haskell).
def certain[A](a: A) = new Random({seed: Long => (seed, a)})
Random[A] - это вычисление, дающее случайное значение типа A. Методы flatMap, map и функция unit служат для составления простых вычислений для построения более сложных. Например, мы создадим два Random[Double] для сборки Random[(Double, Double)].
Монадическая случайная точка
Теперь, когда у нас есть монада, мы готовы пересмотреть randomPoint и randomDouble. Теперь мы определяем их по-разному как функции, дающие Random[Double] и Random[(Double, Double)]
def randomDouble(): Random[Double] = new Random({seed: Long => ... })
def randomPoint(): Random[(Double, Double)] =
randomDouble().flatMap(x => randomDouble().flatMap(y => certain(x, y))
Эта реализация лучше предыдущей, поскольку она использует общий инструмент (flatMap и certain) для составления двух вызовов Random[Double] и сборки Random[(Double, Double)].
Теперь можно повторно использовать этот инструмент для создания дополнительных функций, генерирующих случайные значения.
Расчет Монте-Карло Pi
Теперь мы можем использовать map для проверки, является ли случайная точка в круге:
def randomCircleTest(): Random[Boolean] =
randomPoint().map {case (x, y) => x * x + y * y <= 1}
Мы также можем определить моделирование Монте-Карло в терминах Random[A]
def monteCarlo(test: Random[Boolean], trials: Int): Random[Double] = ...
и, наконец, функция вычисления PI
def pi(trials: Int): Random[Double] = ....
Все эти функции чисты. Побочные эффекты возникают только тогда, когда мы наконец применяем функцию pi для получения значения pi.