For-Each and For-Yield

Note: This feature is experimental and not yet intended for use.

In Flix, as in other functional programming languages, most iteration is expressed either through recursion or with combinators (e.g. map or foldLeft).

That said, Flix has syntactic sugar for two common types of loops: for-each and for yield.

For Each

The for-each construct is useful for iterating over a collection and apply some transformation to each element and works particularly well with mutable collections. This is due to the fact that the for-each loop is actually just syntactic sugar for a call to Iterable.foreach which has return type Unit. Thus, for the loop to be useful the body of the loop should have an effect. However, before going any further an example is in order.

To use the for-each loop an instance of Iterable on the collection is required. For this example we will use a MutList.

def main(): Unit & Impure = region r {
    use MutList.push!;

    let l = new MutList(r)
        !> push!(1)
        !> push!(2)
        !> push!(3);

    foreach (x <- l)
        println(x)
}

For Yield

Flix also supports a for-yield construct which is similar to Scala's for-comprehensions or to Haskell's list comprehension. The for-yield construct is simply syntactic sugar for uses of point and flatMap (which requires an instance of the Monad type class). The for-yield construct also supports a guard-expression which, when used, additionally requires an instance of the MonadZero type class.

The for-yield expression:

let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
for (x <- l1; y <- l2)
    yield (x, y)

evaluates to the list:

(1, 1) :: (1, 2) :: (2, 1) :: (2, 2) :: Nil

The for-yield expression:

let l1 = 1 :: 2 :: Nil;
let l2 = 1 :: 2 :: Nil;
for (x <- l1; y <- l2; if x < y)
    yield (x, y)

evaluates to the list:

(1, 2) :: Nil

We can use for-yield on any data type which implements the Monad type class. For example, we can iterate through non-empty lists:

let l1 = Nel(1, 2 :: Nil);
let l2 = Nel(1, 2 :: Nil);
for (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 a non-empty list because such an if-guard requires an instance of the MonadZero type class which is not implemented by non-empty list. Intuitively, we cannot use a filter in combination with a data structures that cannot be empty.