Monadic For-Yield
Flix supports a monadic forM-yield construct similar to Scala's
for-comprehensions and Haskell's do notation. The forM construct is syntactic
sugar for uses of point
and flatMap
(which are provided by the Monad
trait). The forM construct also supports a guard-expression that uses
empty
(which is provided by the MonadZero
trait).
For example, the monadic forM
expression:
let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
forM (x <- l1; y <- l2)
yield (x, y)
evaluates to the list:
(1, 1) :: (1, 2) :: (2, 1) :: (2, 2) :: Nil
Using Guard Expressions
We can use guard expressions in forM
expressions. For example, the program:
let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
forM (x <- l1; y <- l2; if x < y)
yield (x, y)
evaluates to the list:
(1, 2) :: Nil
Working with Options and Results
We can also use forM
to work with the Option
data type. For example:
def divide(x: Int32, y: Int32): Option[Int32] =
if (y == 0) None else Some(x / y)
def f(): Option[Int32] =
forM (
x <- divide(5, 2);
y <- divide(x, 8);
z <- divide(9, y)
) yield x + y + z
Here the function f
returns None
since x = 5 / 2 = 2
and 2 / 8 = 0
hence
the last division fails.
Similarly, we can use forM
to work with the Result[e, t]
data type. For
example:
def main(): Result[String, Unit] \ IO =
println("Please enter your first name, last name, and age:");
forM (
fstName <- Console.readLine();
lstName <- Console.readLine();
ageLine <- Console.readLine();
ageNum <- Int32.parse(10, ageLine)
) yield {
println("Hello ${lstName}, ${fstName}.");
println("You are ${ageNum} years old!")
}
Here main
prompts the user to enter their first name, last name, and age. Each
call to Console.readLine
returns a Result[String, String]
value which is
either an error or the input string. Thus the local variables fstName
,
lstName
, and ageLine
are String
s. We parse ageLine
into an Int32
using
Int32.parse
, which returns a Result[String, Int32]
value. If every operation
is successful then we print a greeting and return Ok(())
(i.e., Ok
of
Unit
). Otherwise, we return an Err(msg)
value.
Working with Other Monads
We can use forM
with other types of Monad
s, including Chain
and Nel
s
(non-empty lists). For example, we can write:
let l1 = Nel(1, 2 :: Nil);
let l2 = Nel(1, 2 :: Nil);
forM (x <- l1; y <- l2)
yield (x, y)
which evaluates to the non-empty list:
Nel((1, 1), (1, 2) :: (2, 1) :: (2, 2) :: Nil)
Note: We cannot use an
if
-guard with non-empty lists because such anif
-guard requires an instance of theMonadZero
trait which is not implemented by non-empty list (since such a list cannot be empty).
Desugaring
The forM
expression is syntactic sugar for uses of Monad.flatMap
,
Applicative.point
, and MonadZero.empty
.
For example, the expression:
let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
forM (x <- l1; y <- l2; if x < y)
yield (x, y)
is de-sugared to:
Monad.flatMap(x ->
Monad.flatMap(y ->
if (x < y)
Applicative.point((x, y))
else
MonadZero.empty(),
l2),
l1);