A relaxed chat room about all things Scala. Beginner questions welcome. http://scala-lang.org/conduct/ applies
IO
will map to pull-based as well
whats the best way to cancel a future in scala?
relevant: https://viktorklang.com/blog/Futures-in-Scala-protips-6.html
Anybody tried this cancellablefuture https://github.com/hbf/tgv/blob/master/src/main/scala/com/dreizak/util/concurrent/CancellableFuture.scala
@joshlelemer
basically the way IO
works is by constructing a datatype, which contains all the info to then interpret the IO
(e.g. the constructor for side effecting code takes an => A
, the constructor for flatMap takes IO[A]
and A => IO[B]
, and they get stored in cases of an ADT). This datatype is then interpreted by the runloop once you unsafeRunSync
at the end of world (typically the top of the call chain, in main
).
The way the interpreter works is basically a while loop over two vars, the current IO[Any]
, and a Stack[Any => IO[Any]]
, you get the current, dispatch on its constructors and do what you need to do (e.g. evaluate the => A
) and keep going with the next. This is a powerful approach because manipulating the stack allows you to implement complex features (e.g. you can shift to a trampoline every n
calls to solve the stack safety problem you would otherwise have). It's also similar to what you mean by "polling": if you don't fetch the next thing off the stop, things just stop executing.
However, the crucial detail is that this by itself is not Interruption, it's suspension: i.e. you have defined a mechanism for an IO to suspend itself and give control back to the main thread (which is how fiber level concurrency is implemented), but interruption means that another IO needs to stop you.
So you need to have a shared (volatile, at the very least) variable which is the interrupt flag, the other IO sets it when it wants to interrupt you, and in your runloop you need to check this variable, and only then suspend yourself when the variable is true.
And that indeed brings up back to the initial question: how often do you check it? And there are different choices: you can check it at every flatMap
, that's very fine grained but also quite expensive. The current choice in IO
is to check it at every async boundary. And as I said, there's another whole bunch of complexity (in library code, not user code) to make sure things are resource safe and finalisers are registered and run correctly, in all scenarios
sealed trait Expression{
def eval: Sum[String, Double] = this match {
case Addition(left, right) => left.eval flatMap { x => right.eval flatMap(y => Success(x + y))}
case Subtraction(left, right) => left.eval flatMap { x => right.eval flatMap(y => Success(x - y))}
}
flatMap
on? Success is one of the members and left and right both have that type
map
, so yield