do vs for 

There are subtle differences in Haskell’s do notation and Scala’s for syntax. Here’s an example of do notation:

foo = do
  x <- Just 3
  y <- Just "!"
  Just (show x ++ y)

Typically one would write return (show x ++ y), but I wrote out Just, so it’s clear that the last line is a monadic value. On the other hand, Scala would look as follows:

scala> def foo = for {
         x <- Some(3)
         y <- Some("!")
       } yield x.toString + y
foo: Option[String]

Looks similar, but there are some differences.

Here are some demonstration of these points:

scala> import collection.immutable.BitSet
import collection.immutable.BitSet

scala> val bits = BitSet(1, 2, 3)
bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> for {
         x <- bits
       } yield x.toFloat
res5: scala.collection.immutable.SortedSet[Float] = TreeSet(1.0, 2.0, 3.0)

scala> for {
         i <- List(1, 2, 3)
         j <- Some(1)
       } yield i + j
res6: List[Int] = List(2, 3, 4)

scala> for {
         i <- Map(1 -> 2)
         j <- Some(3)
       } yield j
res7: scala.collection.immutable.Iterable[Int] = List(3)

Implementing actM 

There are several DSLs around in Scala that transforms imperative-looking code into monadic or applicative function calls using macros:

Covering full array of Scala syntax in the macro is hard work, but by copy-pasting code from Async and Effectful I put together a toy macro that supports only simple expressions and vals. I’ll omit the details, but the key function is this:

  def transform(group: BindGroup, isPure: Boolean): Tree =
    group match {
      case (binds, tree) =>
        binds match {
          case Nil =>
            if (isPure) q"""$monadInstance.pure($tree)"""
            else tree
          case (name, unwrappedFrom) :: xs =>
            val innerTree = transform((xs, tree), isPure)
            val param = ValDef(Modifiers(Flag.PARAM), name, TypeTree(), EmptyTree)
            q"""$monadInstance.flatMap($unwrappedFrom) { $param => $innerTree }"""

Here’s how we can use actM:

scala> import cats._, cats.instances.all._
import cats._
import cats.instances.all._

scala> :paste
// Entering paste mode (ctrl-D to finish)
object Catnip {
  implicit class IdOp[A](val a: A) extends AnyVal {
    def some: Option[A] = Some(a)
  def none[A]: Option[A] = None
import Catnip._

// Exiting paste mode, now interpreting.

defined object Catnip
import Catnip._

scala> import example.MonadSyntax._
import example.MonadSyntax._

scala> actM[Option, String] {
         val x =
         val y = "!"
         x.toString + y
res8: Option[String] = Some(3!) expands to a Monad[F].flatMap(fa)() call. So the above code expands into:

scala> Monad[Option].flatMap[String, String]({
         val fa0: Option[Int] = 3.some
         Monad[Option].flatMap[Int, String](fa0) { (arg0: Int) => {
           val next0: Int = arg0
           val x: Int = next0
           val fa1: Option[String] = "!".some
           Monad[Option].flatMap[String, String](fa1)((arg1: String) => {
             val next1: String = arg1
             val y: String = next1
             Monad[Option].pure[String](x.toString + y)
       }) { (arg2: String) => Monad[Option].pure[String](arg2) }
res9: Option[String] = Some(3!)

Let’s see if this can prevent auto conversion from Option to List.

scala> actM[List, Int] {
         val i = List(1, 2, 3).next
         val j =
         i + j
<console>:32: error: exception during macro expansion:
scala.reflect.macros.TypecheckException: type mismatch;
 found   : fa$macro$15.type (with underlying type Option[Int] @scala.reflect.internal.annotations.uncheckedBounds)
 required: List[?]
	at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:34)
	at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:28)
	at scala.reflect.macros.contexts.Typers$$anonfun$3.apply(Typers.scala:24)
	at scala.reflect.macros.contexts.Typers$$anonfun$3.apply(Typers.scala:24)
	at scala.reflect.macros.contexts.Typers$$anonfun$withContext$1$1.apply(Typers.scala:25)
	at scala.reflect.macros.contexts.Typers$$anonfun$withContext$1$1.apply(Typers.scala:25)
	at scala.reflect.macros.contexts.Typers$$anonfun$1.apply(Typers.scala:23)
	at scala.reflect.macros.contexts.Typers$$anonfun$1.apply(Typers.scala:23)
	at scala.reflect.macros.contexts.Typers$class.withContext$1(Typers.scala:25)
	at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2.apply(Typers.scala:28)
	at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2.apply(Typers.scala:28)
	at scala.reflect.internal.Trees$class.wrappingIntoTerm(Trees.scala:1716)
	at scala.reflect.internal.SymbolTable.wrappingIntoTerm(SymbolTable.scala:16)
	at scala.reflect.macros.contexts.Typers$class.withWrapping$1(Typers.scala:26)
	at scala.reflect.macros.contexts.Typers$class.typecheck(Typers.scala:28)
	at scala.reflect.macros.contexts.Context.typecheck(Context.scala:6)
	at scala.reflect.macros.contexts.Context.typecheck(Context.scala:6)
	at example.internal.ActMTransform$class.actMTransform(ActMTransform.scala:27)
	at example.internal.ActMMacro$$anon$1.actMTransform(ActMMacro.scala:8)
	at example.internal.ActMBase.actMImpl(ActMBase.scala:13)
	at example.internal.ActMImpl$.actMImpl(ActMImpl.scala:9)

       actM[List, Int] {

The error message is a bit rough, but we were able to catch this at compile-time. This will also work for any monads including Future.

scala> :paste
// Entering paste mode (ctrl-D to finish)
val x = {
  import scala.concurrent.{ExecutionContext, Future}
  actM[Future, Int] {
    val i = Future { 1 }.next
    val j = Future { 2 }.next
    i + j

// Exiting paste mode, now interpreting.

x: scala.concurrent.Future[Int] = List()

scala> x.value
res11: Option[scala.util.Try[Int]] = Some(Success(3))

This macro is incomplete toy code, but it demonstrates potential usefulness for having something like this.