在猫中实施while(true)

Piu*_*130 3 scala scala-cats

如何在cat中实现以下循环?

首先(正常while(true)循环):

while(true) { doSomething() }
Run Code Online (Sandbox Code Playgroud)

第二个(while(true)循环递增):

var i = 1
while(true) { i +=1; doSomething() }
Run Code Online (Sandbox Code Playgroud)

第三(while(true)里面有几个自变量):

var x = 1
var y = 2
while(true) {
  x = someCalculation()
  y = otherCalculation()
  doSomething()
}
Run Code Online (Sandbox Code Playgroud)

And*_*kin 5

我认为您的问题有些不恰当,但是它以一种有趣的方式令人不适,因此也许我应该尝试解释一下我的意思。

“我如何实施

var i = 1
while(true) { i +=1; doSomething() }
Run Code Online (Sandbox Code Playgroud)

在Cats中”将被简单地回答:与在普通Scala中实现它的方式完全相同,而无需使用任何库。在这种情况下,Cats无法使您实现在运行时表现出极大不同的任何东西。要做的是更精确地表达每段代码所具有的(副作用),并将其编码为类型级别信息,可以在编译时在静态类型检查期间对其进行验证。

因此,问题不应该是

“我怎么在猫身上 X?”

反而

“如何证明 /明确表明我的代码使用Cats有(或没有)某些副作用?”。

您的示例中的while循环仅在中执行了一些副作用doSomething(),并且在需要i时就使变量的可变状态陷入混乱,而没有在组成子表达式的类型中使它明确。

现在,您可能会喜欢effects.IO,并且至少将主体包裹在doSomethingIO,从而使它明确执行输入/输出操作(在这里:打印到StdOut):

// Your side-effectful `doSomething` function in IO
def doSomething: IO[Unit] = IO { println("do something") }
Run Code Online (Sandbox Code Playgroud)

现在,您可能会问如何以这种方式记下循环,以使其显然也可以执行此类IO操作。您可以执行以下操作:

// Literally `while(true) { ... }`
def whileTrue_1: IO[Unit] =
  Monad[IO].whileM_(IO(true)) { doSomething }

// Shortcut using `foreverM` syntax
import cats.syntax.flatMap._
def whileTrue_2: IO[Nothing] = doSomething.foreverM

// Use `>>` from `syntax.flatMap._`
def whileTrue_3: IO[Unit] = doSomething >> whileTrue_3
Run Code Online (Sandbox Code Playgroud)

现在,如果您想将可变变量i放入混合中,则可以将可变存储器的写入/读取视为另一个IO操作:

// Treat access to mutable variable `i` as
// yet another IO side effect, do something
// with value of `i` again and again.
def whileInc_1: IO[Unit] = {
  var i = 1
  def doSomethingWithI: IO[Unit] = IO {
    println(s"doing sth. with $i")
  }

  Monad[IO].whileM_(IO(true)) {
    for {
      _ <- IO { i += 1 }
      _ <- doSomethingWithI
    } yield ()
  }
}
Run Code Online (Sandbox Code Playgroud)

另外,您可能会决定跟踪所有状态的访问/更改i足够重要,以至于要使其明确,例如,使用StateTmonad转换器来跟踪类型的状态Int

// Explicitly track variable `i` in a state monad
import cats.data.StateT
import cats.data.StateT._
def whileInc_2: IO[Unit] = {

  // Let's make `doSthWithI` not too boring,
  // so that it at least accesses the state
  // with variable `i`
  def doSthWithI: StateT[IO, Int, Unit] =
    for {
      i <- get[IO, Int]
      _ <- liftF(IO { println(i) })
    } yield ()

  // Define the loop
  val loop = Monad[StateT[IO, Int, ?]].whileM_(
    StateT.pure(true)
  ) {
    for {
      i <- get[IO, Int]
      _ <- set[IO, Int](i + 1)
      _ <- doSthWithI
    } yield ()
  }

  // The `_._2` is there only to make the
  // types match up, it's never actually used,
  // because the loop runs forever.
  loop.run(1).map(_._2)
}
Run Code Online (Sandbox Code Playgroud)

它对两个变量x和的作用类似y(仅使用(Int, Int)而不是Int作为状态)。

诚然,这段代码看起来有些冗长,尤其是最后一个示例开始看起来像是企业版的“嗡嗡声”,但关键是,如果您始终将这些技术应用于您的代码库,则不必深入研究一个函数的主体,仅凭其签名就可以(或不能)做一个相当好的主意。反过来,这在尝试理解代码时是有利的(您可以通过浏览签名来了解代码的功能,而无需阅读正文中的代码),并且还迫使您编写更易于测试的简单代码(在至少是这个主意)。