Search code examples
scalaconcurrencyio-monad

How IO monad makes concurrency easy to do in Scala?


I watch this video and starts at 6min35s, it mentioned this graph:

enter image description here

saying that the IO Monad makes it easy to handle concurrency. I got confused on this: how does it work? How do the two for comprehension enable the concurrency (the computation of d and f)?


Solution

  • No, it doesn't enable the concurrency

    for comprehensions only help you omitting several parentheses and indents.

    The code you referred, translate to [flat]map is strictly equivalent to:

    async.boundedQueue[Stuff](100).flatMap{ a => 
      val d = computeB(a).flatMap{
        b => computeD(b).map{ result =>
          result
        }
      }
    
      val f = computeC(a).flatMap{ c =>
        computeE(c).flatMap{ e =>
          computeF(e).map{ result =>
            result
          }
        }
      }
    
      d.merge(f).map(g => g)
    }
    

    See, it only helps you omitting several parentheses and indents (joke)

    The concurrency is hidden in flatMap and map

    Once you understand how for is translated to flatMap and map, you can implement your concurrency inside them.

    As map takes a function as argument, it doesn't mean that the function is executed during execution of map function, you can defer the function to another thread or run it latter. This is how concurrency implemented.

    Take Promise and Future as example:

    val future: Future = ```some promise```
    val r: Future = for (v <- future) yield doSomething(v)
    // or
    val r: Future = future.map(v => doSomething(v))
    r.wait
    

    The function doSomething is not executed during the execution of Future.map function, instead it is called when the promise commits.

    Conclusion

    How to implement concurrency using for syntax suger:

    1. for will convert into flatMap and map by scala compiler
    2. Write you flatMap and map, where you will get a callback function from argument
    3. Call the function you got whenever and wherever you like

    Further reading

    The flow control feature of many languages share a same property, they are like delimited continuation shift/reset, where they capture the following execution upto a scope into an function.

    JavaScript:

    async function() {
      ...
      val yielded = await new Promise((resolve) => shift(resolve))
                                      // resolve will captured execution of following statements upto end of the function.
      ...captured
    }
    

    Haskell:

    do {
      ...
      yielded_monad <- ```some monad``` -- shift function is >>= of the monad
      ...captured
    }
    

    Scala:

    for {
      ...
      yielded_monad <- ```some monad``` // shift function is flatMap/map of the monad
      ...captured
    } yield ...
    

    next time you see a language feature which capture following execution into a function, you know you can implement flow control using the feature.

    The difference between delimited continuation and call/cc is that call/cc capture the whole following execution of the program, but delimited continuation has a scope.