在猫中实现 while(true)
Implement while(true) in cats
如何在猫中实现以下循环?
首先(正常的 while(true) 循环):
while(true) { doSomething() }
第二个(带增量的 while(true) 循环):
var i = 1
while(true) { i +=1; doSomething() }
第三个(while(true)里面有几个自变量):
var x = 1
var y = 2
while(true) {
x = someCalculation()
y = otherCalculation()
doSomething()
}
在您的情况下,while(true)
循环的功能替代方案只是递归函数:
@tailrec
def loop(x: Int, y: Int) { //every call of loop will receive updated state
doSomething()
loop(someCalculation(), otherCalculation()) //next iteration with updated state
}
loop(1,2); //start "loop" with initial state
这里很重要的是@tailrec
注解。它检查对 loop
的递归调用是否在尾部位置,因此可以应用 tail-call optimalization。如果不是这种情况,您的 "loop" 会导致 WhosebugException
。
一个有趣的事实是,优化后的函数在字节码中看起来与 while
循环非常相似。
这种方法与猫并没有直接关系,而是与函数式编程有关。递归在 FP 中非常常用。
我认为你的问题有点 ill-posed,但它 ill-posed 的方式很有趣,所以也许我应该试着解释一下我的意思。
询问“我如何实施
var i = 1
while(true) { i +=1; doSomething() }
在 Cats 中的回答很简单:与在不使用任何库的情况下在普通 Scala 中实现它的方式完全相同。在这种特殊情况下,Cats 不会启用您实现任何在运行时行为截然不同的东西。它使您能够做的是更准确地表达每段代码的(副作用),并将其编码为类型级别信息,可以在编译时验证在静态类型检查期间。
所以,问题不应该是
"How do I do X in Cats?"
而是
"How do I prove / make explicit that my code does (or does not) have certain side effects using Cats?".
您的示例中的 while 循环只是在 doSomething()
中执行一些副作用,并且它会在需要时随时扰乱变量 i
的可变状态,而不会在类型中明确显示组成子表达式的数量。
现在你可以采用 effects.IO
之类的东西,并且至少将 doSomething
的主体包裹在 IO
中,从而明确表示它执行 input/output操作(此处:打印到 StdOut):
// Your side-effectful `doSomething` function in IO
def doSomething: IO[Unit] = IO { println("do something") }
现在您可能会问如何以这样一种方式写下循环,使得它执行这样的操作也变得显而易见 IO-operations。你可以这样做:
// 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
现在,如果您想将可变变量 i
放入混合中,您可以将可变内存的 writing/reading 视为另一个 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 ()
}
}
或者,您可能会决定跟踪 i
状态的所有访问/更改非常重要,以至于您希望将其明确化,例如使用 StateT
monad transformer 来跟踪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)
}
它与两个变量 x
和 y
的功能类似(只需使用 (Int, Int)
而不是 Int
作为状态)。
诚然,这段代码看起来有点冗长,尤其是最后一个示例开始看起来像 enterprise-edition fizz-buzz,但关键是如果您始终如一地将这些技术应用于您的代码库,您不必深入研究函数体即可仅根据其签名就可以很好地了解它可以(或不能)做什么。当你试图理解代码时,这反过来是有利的(你可以通过浏览签名来理解它的作用,而无需阅读正文中的代码),并且它还迫使你编写更容易测试的更简单的代码(在至少这是个主意。
如何在猫中实现以下循环?
首先(正常的 while(true) 循环):
while(true) { doSomething() }
第二个(带增量的 while(true) 循环):
var i = 1
while(true) { i +=1; doSomething() }
第三个(while(true)里面有几个自变量):
var x = 1
var y = 2
while(true) {
x = someCalculation()
y = otherCalculation()
doSomething()
}
在您的情况下,while(true)
循环的功能替代方案只是递归函数:
@tailrec
def loop(x: Int, y: Int) { //every call of loop will receive updated state
doSomething()
loop(someCalculation(), otherCalculation()) //next iteration with updated state
}
loop(1,2); //start "loop" with initial state
这里很重要的是@tailrec
注解。它检查对 loop
的递归调用是否在尾部位置,因此可以应用 tail-call optimalization。如果不是这种情况,您的 "loop" 会导致 WhosebugException
。
一个有趣的事实是,优化后的函数在字节码中看起来与 while
循环非常相似。
这种方法与猫并没有直接关系,而是与函数式编程有关。递归在 FP 中非常常用。
我认为你的问题有点 ill-posed,但它 ill-posed 的方式很有趣,所以也许我应该试着解释一下我的意思。
询问“我如何实施
var i = 1
while(true) { i +=1; doSomething() }
在 Cats 中的回答很简单:与在不使用任何库的情况下在普通 Scala 中实现它的方式完全相同。在这种特殊情况下,Cats 不会启用您实现任何在运行时行为截然不同的东西。它使您能够做的是更准确地表达每段代码的(副作用),并将其编码为类型级别信息,可以在编译时验证在静态类型检查期间。
所以,问题不应该是
"How do I do X in Cats?"
而是
"How do I prove / make explicit that my code does (or does not) have certain side effects using Cats?".
您的示例中的 while 循环只是在 doSomething()
中执行一些副作用,并且它会在需要时随时扰乱变量 i
的可变状态,而不会在类型中明确显示组成子表达式的数量。
现在你可以采用 effects.IO
之类的东西,并且至少将 doSomething
的主体包裹在 IO
中,从而明确表示它执行 input/output操作(此处:打印到 StdOut):
// Your side-effectful `doSomething` function in IO
def doSomething: IO[Unit] = IO { println("do something") }
现在您可能会问如何以这样一种方式写下循环,使得它执行这样的操作也变得显而易见 IO-operations。你可以这样做:
// 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
现在,如果您想将可变变量 i
放入混合中,您可以将可变内存的 writing/reading 视为另一个 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 ()
}
}
或者,您可能会决定跟踪 i
状态的所有访问/更改非常重要,以至于您希望将其明确化,例如使用 StateT
monad transformer 来跟踪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)
}
它与两个变量 x
和 y
的功能类似(只需使用 (Int, Int)
而不是 Int
作为状态)。
诚然,这段代码看起来有点冗长,尤其是最后一个示例开始看起来像 enterprise-edition fizz-buzz,但关键是如果您始终如一地将这些技术应用于您的代码库,您不必深入研究函数体即可仅根据其签名就可以很好地了解它可以(或不能)做什么。当你试图理解代码时,这反过来是有利的(你可以通过浏览签名来理解它的作用,而无需阅读正文中的代码),并且它还迫使你编写更容易测试的更简单的代码(在至少这是个主意。