Если вы хотите итерировать использование монады безопасным для стека способом, то в классе типов Monad
реализован метод tailRecM
:
// assuming random generated [-1.0,1.0]
def calculatePi[F[_]](iterations: Int)
(random: => F[Double])
(implicit F: Monad[F]): F[Double] = {
case class Iterations(total: Int, inCircle: Int)
def step(data: Iterations): F[Either[Iterations, Double]] = for {
x <- random
y <- random
isInCircle = (x * x + y * y) < 1.0
newTotal = data.total + 1
newInCircle = data.inCircle + (if (isInCircle) 1 else 0)
} yield {
if (newTotal >= iterations) Right(newInCircle.toDouble / newTotal.toDouble * 4.0)
else Left(Iterations(newTotal, newInCircle))
}
// iterates until Right value is returned
F.tailRecM(Iterations(0, 0))(step)
}
calculatePi(10000)(Future { Random.nextDouble }).onComplete(println)
Он использует параметр по имени, потому что вы можетепопробуйте передать что-то вроде Future
(даже если Future
не законно), которые стремятся, так что вам придется снова и снова оценивать одно и то же.По крайней мере, по имени param у вас есть шанс передать рецепт случайного побочного эффекта.Конечно, если мы используем Option
, List
в качестве монады, содержащей наше «случайное» число, мы также должны ожидать забавных результатов.
Правильное решение будет использовать что-то, что гарантирует, что F[A]
лениво оценивается, и любой побочный эффект внутри оценивается каждый раз, когда вам нужно значение изнутри.Для этого вам в основном нужно использовать некоторые классы типов эффектов, например, Sync
из Cats Effects.
def calculatePi[F[_]](iterations: Int)
(random: F[Double])
(implicit F: Sync[F]): F[Double] = {
...
}
calculatePi(10000)(Coeval( Random.nextDouble )).value
calculatePi(10000)(Task( Random.nextDouble )).runAsync
В качестве альтернативы, если вам не так важна чистота, вы можете пропустить побочную функциюили объект вместо F[Int]
для генерации случайных чисел.
// simplified, hardcoded F=Coeval
def calculatePi(iterations: Int)
(random: () => Double): Double = {
case class Iterations(total: Int, inCircle: Int)
def step(data: Iterations) = Coeval {
val x = random()
val y = random()
val isInCircle = (x * x + y * y) < 1.0
val newTotal = data.total + 1
val newInCircle = data.inCircle + (if (isInCircle) 1 else 0)
if (newTotal >= iterations) Right(newInCircle.toDouble / newTotal.toDouble * 4.0)
else Left(Iterations(newTotal, newInCircle))
}
Monad[Coeval].tailRecM(Iterations(0, 0))(step).value
}