Applicative For-Yield
In addition to the monadic forM
expression, Flix supports an applicative
forA
expression that builds on the Applicative
trait. The forA
construct makes it simple to write error-handling code which uses the
Validation[e, t]
data type.
Working with Validations
We can use the forA
expression to validate user input while collecting all
errors.
enum Connection(String, String)
enum InvalidInput {
case InvalidUserName,
case InvalidPassword
}
def validateUser(s: String): Validation[InvalidInput, String] =
if (8 <= String.length(s) and String.forAll(Char.isLetter, s))
Validation.Success(s)
else
Validation.Failure(Nec.singleton(InvalidInput.InvalidUserName))
def validatePass(s: String): Validation[InvalidInput, String] =
if (12 <= String.length(s) and String.length(s) <= 20)
Validation.Success(s)
else
Validation.Failure(Nec.singleton(InvalidInput.InvalidPassword))
def connect(u: String, p: String): Validation[InvalidInput, Connection] =
forA (
user <- validateUser(u);
pass <- validatePass(p)
) yield Connection.Connection(user, pass)
The expression:
connect("Lucky Luke", "Ratata")
evaluates to:
Failure(Nec#{InvalidUserName, InvalidPassword})
which contains both input validation errors. On the other hand, the expression:
connect("luckyluke", "password12356789")
evaluates to:
Success(Connection(luckyluke, password12356789))
Applicatives are Independent Computations
We can write a monadic forM
expression where the result of one monadic
operation is used as the input to another monadic operation. For example:
forM(x <- Some(123); y <- Some(x))
yield (x, y)
Here the value of y
depends on x
. That is, the computation of x
and y
are not independent.
If we try to same with the applicative forA
expression:
forA(x <- Some(123); y <- Some(x))
yield (x, y)
then the Flix compiler emits a compiler error:
❌ -- Resolution Error --------------
>> Undefined name 'x'.
10 | y <- Some(x)
^
name not found
because the computations of x
and y
are independent and hence the value of
x
is not in scope when we define the value of y
.
Desugaring
The forA
expression is syntactic sugar for uses of Functor.map
and
Applicative.ap
.
For example, the expression:
let o1 = Some(21);
let o2 = Some(42);
forA(x <- o1; y <- o2)
yield x + y;
is de-sugared to:
Applicative.ap(Functor.map(x -> y -> x + y, o1), o2)