Compreendendo a mônada aleatória no Scala
Este é um seguimento do meu anteriorPergunta, questão
Travis Brown apontou quejava.util.Random
tem efeito colateral e sugeriu uma mônada aleatóriaRng
biblioteca para tornar o código puramente funcional. Agora, estou tentando construir uma mônada aleatória simplificada sozinha para entender como ela funciona.
Isso faz sentido ? Como você corrige / aprimora a explicação abaixo?
Gerador AleatórioPrimeiro plagiamos uma função geradora aleatória dejava.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) = ...
Observe quenext
retornaambos a nova semente e o valor, e não apenas o valor. Precisamos passar a nova semente para outra invocação de função.
Agora vamos escrever uma função para gerar um ponto aleatório em um quadrado de unidade.
Suponha que tenhamos uma função para gerar um duplo aleatório no intervalo [0, 1]
def randomDouble(seed: Long): (Long, Double) = ... // some bit magic
Agora podemos escrever uma função para gerar um ponto aleatório.
def randomPoint(seed: Long): (Long, (Double, Double)) = {
val (seed1, x) = randomDouble(seed)
val (seed2, y) = randomDouble(seed1)
(seed2, (x, y))
}
Até agora, tudo bem e ambosrandomDouble
erandomPoint
são puros. O único problema é que compomosrandomDouble
construirrandomPoint
Ad hoc. Nós não temos umgenérico ferramenta para compor funções com valores aleatórios.
Agora vamos definir umgenérico ferramenta para compor funções com valores aleatórios. Primeiro, generalizamos o tipo derandomDouble
:
type Random[A] = Long => (Long, A) // generate a random value of type A
e, em seguida, crie uma classe de wrapper em torno dele.
class Random[A](run: Long => (Long, A))
Precisamos do wrapper para definir métodosflatMap
(Comoligar em Haskell) emap
usado porpara compreensão.
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))})
}
Agora adicionamos umfábrica-função para criar uma trivialRandom[A]
(que é absolutamente determinístico em vez de "aleatório"). Este é umRetorna funciona comoRetorna em Haskell).
def certain[A](a: A) = new Random({seed: Long => (seed, a)})
Random[A]
é umcomputação produzindo valor aleatório do tipo A. Os métodosflatMap
, map
e funçãounit
serve paracomposição cálculos simples para criar outros mais complexos. Por exemplo, vamos compor doisRandom[Double]
construirRandom[(Double, Double)]
.
Agora, quando temos uma mônada, estamos prontos para revisitarrandomPoint
erandomDouble
. Agora, nós os definimos de maneira diferente como funções que produzemRandom[Double]
eRandom[(Double, Double)]
def randomDouble(): Random[Double] = new Random({seed: Long => ... })
def randomPoint(): Random[(Double, Double)] =
randomDouble().flatMap(x => randomDouble().flatMap(y => certain(x, y))
Esta implementação éMelhor do que o anterior, uma vez que utiliza umgenérico ferramenta (flatMap
ecertain
) para compor duas chamadas deRandom[Double]
e construirRandom[(Double, Double)]
.
Agora pode reutilizar esta ferramenta para criar mais funções gerando valores aleatórios.
Cálculo de Pi de Monte CarloAgora podemos usarmap
para testar se um ponto aleatório está no círculo:
def randomCircleTest(): Random[Boolean] =
randomPoint().map {case (x, y) => x * x + y * y <= 1}
Também podemos definir uma simulação de Monte-Carlo em termos deRandom[A]
def monteCarlo(test: Random[Boolean], trials: Int): Random[Double] = ...
e, finalmente, a função para calcular PI
def pi(trials: Int): Random[Double] = ....
Todas essas funções são puras. Os efeitos colaterais ocorrem apenas quando finalmente aplicamos opi
para obter o valor de pi.