我正在尝试了解如何在Scala中利用monad来解决简单的问题,以此来增强我的熟悉度。一个简单的问题是使用函数随机数生成器估算PI。我将在下面的代码中包含一个基于流的简单方法。
我正在寻求帮助,以将其转换为单子方法。例如,是否有惯用的方式将此代码转换为以堆栈安全的方式使用状态(和其他monad)?
trait RNG {
def nextInt: (Int, RNG)
def nextDouble: (Double, RNG)
}
case class Point(x: Double, y: Double) {
val isInCircle = (x * x + y * y) < 1.0
}
object RNG {
def nonNegativeInt(rng: RNG): (Int, RNG) = {
val (ni, rng2) = rng.nextInt
if (ni > 0) (ni, rng2)
else if (ni == Int.MinValue) (0, rng2)
else (ni + Int.MaxValue, rng2)
}
def double(rng: RNG): (Double, RNG) = {
val (ni, rng2) = nonNegativeInt(rng)
(ni.toDouble / Int.MaxValue, rng2)
}
case class Simple(seed: Long) extends RNG {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = Simple(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
def nextDouble: (Double, RNG) = {
val (n, nextRNG) = nextInt
double(nextRNG)
}
}
}
object PI {
import RNG._
def doubleStream(rng: Simple):Stream[Double] = rng.nextDouble match {
case (d:Double, next:Simple) => d #:: doubleStream(next)
}
def estimate(rng: Simple, iter: Int): Double = {
val doubles = doubleStream(rng).take(iter)
val inside = (doubles zip doubles.drop(3))
.map { case (a, b) => Point(a, b) }
.filter(p => p.isInCircle)
.size * 1.0
(inside / iter) * 4.0
}
}
// > PI.estimate(RNG.Simple(10), 100000)
// res1: Double = 3.14944
我怀疑我是从replicateM
猫的单子中寻找Applicative
之类的东西,但是我不确定如何排列类型或如何以某种方式做到这一点? t将中间结果累积在内存中。或者,是否有一种方法可以通过for
的理解来迭代建立Point
呢?
答案 0 :(得分:5)
您想要以安全的堆栈方式使用monad进行迭代的ID,然后在CREATE OR REPLACE FUNCTION func_increasement()
RETURNS trigger AS
$$
BEGIN
UPDATE ArrayB SET number = coalesce(number,0) + 1
WHERE ArrayB.id = NEW.arraya_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
类型类中实现了一个tailRecM
方法:
Monad
它使用了基于名字的参数,因为您可以尝试传递类似// 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
,Option
用作持有“随机”数字的单子,那么我们也应该期待有趣的结果。
正确的解决方案将使用可以确保对此List
进行惰性计算的方法,并且每次您需要内部提供的值时,都可以评估内部的任何副作用。为此,您基本上必须使用一些Effect类型类,例如F[A]
来自Cats Effects。
Sync
或者,如果您不太关心纯度,则可以传递副作用函数或对象而不是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]
答案 1 :(得分:0)
这是我的朋友Charles Miller提出的另一种方法。因为它直接使用RNG
,所以它更直接一些,但是它遵循@Mateusz Kubuszok提供的与利用Monad
相同的方法。
关键区别在于它利用了State
单子,因此我们可以通过计算使RNG
状态变为线程,并使用“纯”随机数生成器生成随机数。
import cats._
import cats.data._
import cats.implicits._
object PICharles {
type RNG[A] = State[Long, A]
object RNG {
def nextLong: RNG[Long] =
State.modify[Long](
seed ⇒ (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
) >> State.get
def nextInt: RNG[Int] = nextLong.map(l ⇒ (l >>> 16).toInt)
def nextNatural: RNG[Int] = nextInt.map { i ⇒
if (i > 0) i
else if (i == Int.MinValue) 0
else i + Int.MaxValue
}
def nextDouble: RNG[Double] = nextNatural.map(_.toDouble / Int.MaxValue)
def runRng[A](seed: Long)(rng: RNG[A]): A = rng.runA(seed).value
def unsafeRunRng[A]: RNG[A] ⇒ A = runRng(System.currentTimeMillis)
}
object PI {
case class Step(count: Int, inCircle: Int)
def calculatePi(iterations: Int): RNG[Double] = {
def step(s: Step): RNG[Either[Step, Double]] =
for {
x ← RNG.nextDouble
y ← RNG.nextDouble
isInCircle = (x * x + y * y) < 1.0
newInCircle = s.inCircle + (if (isInCircle) 1 else 0)
} yield {
if (s.count >= iterations)
Right(s.inCircle.toDouble / s.count.toDouble * 4.0)
else
Left(Step(s.count + 1, newInCircle))
}
Monad[RNG].tailRecM(Step(0, 0))(step(_))
}
def unsafeCalculatePi(iterations: Int) =
RNG.unsafeRunRng(calculatePi(iterations))
}
}
感谢Charles&Mateusz的帮助!