Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a short-circuit with IO monad in Scala

Tags:

monads

scala

I use a standard IO monad.

And at some point, i need to shortcircuit. On a given condition, i don't want to run the following ios.

Here is my solution, but I found it too verbose and not elegant :

  def shortCircuit[A](io: IO[A], continue: Boolean) =
    io.map(a => if (continue) Some(a) else None)

  for {
    a <- io
    b <- shortCircuit(io, a == 1)
    c <- shortCircuit(io, b.map(_ == 1).getOrElse(false))
    d <- shortCircuit(io, b.map(_ == 1).getOrElse(false))
    e <- shortCircuit(io, b.map(_ == 1).getOrElse(false))
  } yield …

For example, for 3rd, 4th and 5th line, I need to repeat the same condition.

Is there a better way ?

like image 404
Yann Moisan Avatar asked Mar 27 '15 02:03

Yann Moisan


1 Answers

You haven't actually short-circuited anything there. You are still running the IOs; you just don't capture the values.

Also, the standard IO monad doesn't define filter (or withFilter), so you can't use guards in your for-comprehension.

Now, if you want just what you've said (same logic, just more DRY), you can always assign a temporary variable in the for comprehension:

for {
  a <- io
  b <- shortCircuit(io, a == 1)
  continue = b.map(_ == 1).getOrElse(false)
  c <- shortCircuit(io, continue)
  d <- shortCircuit(io, continue)
  e <- shortCircuit(io, continue)
} yield …

But if you actually want to short-circuit, you will have to break apart the cases in some way. Here's one possibility, assuming you just want to pack everything into an array so the return type is simple, and your IO companion object has an apply method that you can use to create something that just returns a value:

io.flatMap(a =>
  if (a == 1) IO(() => Array(a))
  else io.flatMap(b =>
    if (b == 1) IO(() => Array(a,b))
    else for {
      c <- io
      d <- io
      e <- io
    } yield Array(a,b,c,d,e)
  )
)

If your return types are more complicated, you may have to work harder with specifying types.

FWIW, it is worth noting the penalty that you pay for keeping things wrapped in monads; without, the same logic would be (for example):

io() match {
  case 1 => Array(1)
  case a => io() match {
    case 1 => Array(a, 1)
    case b => Array(a, b, io(), io(), io())
  }
}

And if you allow returns, you get:

val a = io()
if (a == 1) return Array(a)
val b = io()
if (b == 1) return Array(a, b)
Array(a, b, io(), io(), io())

It's also possible in principle to decorate the IO monad with extra methods that help out a bit, but the standard withFilter won't work so you won't be able to use the for-comprehension syntactic sugar.

like image 84
Rex Kerr Avatar answered Nov 15 '22 07:11

Rex Kerr