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)