用Monadic方法估算Scala中的PI

时间:2019-04-16 14:24:14

标签: scala monads scala-cats

我正在尝试了解如何在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呢?

2 个答案:

答案 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是不合法的),它们很渴望,因此您最终将对其进行评估事情一次又一次。至少使用名字参数,您有机会传递一个副作用随机的配方。当然,如果我们将FutureOption用作持有“随机”数字的单子,那么我们也应该期待有趣的结果。

正确的解决方案将使用可以确保对此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的帮助!