如何在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)
我认为您的问题有些不恰当,但是它以一种有趣的方式令人不适,因此也许我应该尝试解释一下我的意思。
问“我如何实施
var i = 1
while(true) { i +=1; doSomething() }
Run Code Online (Sandbox Code Playgroud)
在Cats中”将被简单地回答:与在普通Scala中实现它的方式完全相同,而无需使用任何库。在这种情况下,Cats无法使您实现在运行时表现出极大不同的任何东西。要做的是更精确地表达每段代码所具有的(副作用),并将其编码为类型级别信息,可以在编译时在静态类型检查期间对其进行验证。
因此,问题不应该是
“我怎么在猫身上做 X?”
反而
“如何证明 /明确表明我的代码使用Cats有(或没有)某些副作用?”。
您的示例中的while循环仅在中执行了一些副作用doSomething(),并且在需要i时就使变量的可变状态陷入混乱,而没有在组成子表达式的类型中使它明确。
现在,您可能会喜欢effects.IO,并且至少将主体包裹在doSomething中IO,从而使它明确执行输入/输出操作(在这里:打印到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作为状态)。
诚然,这段代码看起来有些冗长,尤其是最后一个示例开始看起来像是企业版的“嗡嗡声”,但关键是,如果您始终将这些技术应用于您的代码库,则不必深入研究一个函数的主体,仅凭其签名就可以(或不能)做一个相当好的主意。反过来,这在尝试理解代码时是有利的(您可以通过浏览签名来了解代码的功能,而无需阅读正文中的代码),并且还迫使您编写更易于测试的简单代码(在至少是这个主意)。