Понимание случайной монады в Scala
Это продолжение моего предыдущеговопрос
Трэвис Браун указал, что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
для этого случая, У нас нетобщий инструмент для составления функций, дающих случайные значения.
Теперь мы определимобщий инструмент для составления функций, дающих случайные значения. Во-первых, мы обобщаем типrandomDouble
:
type Random[A] = Long => (Long, A) // generate a random value of type A
а затем создайте класс-оболочку вокруг него.
class Random[A](run: Long => (Long, A))
Нам нужна оболочка для определения методовflatMap
(какпривязывать в Хаскеле) и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))})
}
Теперь мы добавляемзавод-функция для создания тривиальногоRandom[A]
(что, кстати, абсолютно детерминировано, а не «случайно»)вернуть функция (каквернуть в Хаскеле).
def certain[A](a: A) = new Random({seed: Long => (seed, a)})
Random[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)]
.
Теперь можно повторно использовать этот инструмент для создания большего количества функций, генерирующих случайные значения.
Монте-Карло расчет ПиТеперь мы можем использовать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
функция, чтобы получить значение пи.