Applicative For-Yield
Note: This documentation is relevant for Flix version 0.35.0 or higher.
In addition to the monadic forM
expression, Flix supports an applicative
forA
expression that builds on the Applicative
type class. 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(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(InvalidPassword))
def connect(u: String, p: String): Validation[InvalidInput, Connection] =
forA (
user <- validateUser(u);
pass <- validatePass(p)
) yield 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)