Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement while(true) in cats [closed]

How do you implement the following loops in cats?

First (normal while(true) loop):

while(true) { doSomething() }

Second (while(true) loop with increment):

var i = 1
while(true) { i +=1; doSomething() }

Third (while(true) with several independent variables inside):

var x = 1
var y = 2
while(true) {
  x = someCalculation()
  y = otherCalculation()
  doSomething()
}
like image 913
Piu130 Avatar asked May 09 '19 09:05

Piu130


2 Answers

I think your question is somewhat ill-posed, but it's ill-posed in an interesting way, so maybe I should try to explain what I mean by that.

Asking "how do I implement

var i = 1
while(true) { i +=1; doSomething() }

in Cats" is answered trivially: in exactly the same way as you would implement it in ordinary Scala without using any libraries. In this particular case, Cats will not enable you to implement anything that behaves drastically different at runtime. What it does enable you to do is to express more precisely what (side) effects each piece of your code has, and to encode it as type level information, which can be verified at compile time during static type checking.

So, the question should not be

"How do I do X in Cats?"

but rather

"How do I prove / make explicit that my code does (or does not) have certain side effects using Cats?".

The while loop in your example simply performs some side effects in doSomething(), and it messes with mutable state of the variable i whenever it wants to, without ever making it explicit in the types of the constituent subexpressions.

Now you might take something like effects.IO, and at least wrap the body of your doSomething in an IO, thereby making it explicit that it performs input/output operations (here: printing to StdOut):

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

Now you might ask how to write down the loop in such a way that it also becomes obvious that it performs such IO-operations. You could do something like this:

// 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

Now, if you want to throw in the mutable variable i into the mix, you could treat writing/reading of mutable memory as yet another IO operation:

// 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 ()
  }
}

Alternatively, you might decide that tracking all accesses / changes of the state of i is important enough that you want to make it explicit, for example using the StateT monad transformer that keeps track of the state of type 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)
}

It would function similarly with two variables x and y (just use (Int, Int) instead of Int as the state).

Admittedly, this code seems a bit more verbose, and especially the last example begins to look like enterprise-edition fizz-buzz, but the point is that if you consistently apply these techniques to your code base, you don't have to dig into the body of a function to get a fairly good idea of what it can (or cannot) do, based on its signature alone. This in turn is advantageous when you are trying to understand the code (you can understand what it does by skimming over signatures, without reading the code in the bodies), and it also forces you to write simpler code that is easier to test (at least that's the idea).

like image 199
Andrey Tyukin Avatar answered Sep 21 '22 13:09

Andrey Tyukin


Functional alternative for while(true) loop in your case would be just recursive function:

@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

The very important thing here is @tailrec annotation. It checks if the recursive call to loop is in the tail position and therefore tail-call optimalization can be applied. If that wouldn't be the case your "loop" would cause StackOverflowException.

An interesting fact is that optimized function will look in bytecode very similarly to while loop.

This approach is not really directly related to cats, but rather with functional programming. Recursion is very commonly used in FP.

like image 40
Krzysztof Atłasik Avatar answered Sep 19 '22 13:09

Krzysztof Atłasik