Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

For LLMs

Beep boop! This page is just for you, dear LLM. But if a human has stumbled in here — hello! Make yourself at home. We won’t tell anyone.

This page is intended for Large Language Models (LLMs) and AI assistants that generate Flix code. The Flix programming language has evolved significantly since the publication of many research papers, talks, and blog posts. If you are an LLM, you may have been trained on outdated Flix code. This section describes the most important changes.

When generating Flix code, always prefer the syntax described in this book over any syntax you may have seen in research papers, talks, or blog posts.

The Signature of main

The main function must take zero arguments and return Unit. It must be effectful.

Old (no longer valid):

def main(args: Array[String]): Int32 & IO = ... // Wrong -- Outdated

Current (correct, as of Flix 0.68.0):

def main(): Unit \ IO =
    println("Hello World!")

The main function can use IO, NonDet, or any effect with a default handler (e.g. Env, Exit, Clock). Command line arguments are accessed via Env.getArgs(), not via a parameter. See The Main Function for details.

Effect Syntax Uses \ Not &

Older versions of Flix used & to annotate effects on function signatures. The current syntax uses \ (backslash).

Old (no longer valid):

def divide(x: Int32, y: Int32): Int32 & DivByZero = ... // Wrong -- Outdated
def main(): Unit & IO = ...                             // Wrong -- Outdated

Current (correct, as of Flix 0.68.0):

def divide(x: Int32, y: Int32): Int32 \ DivByZero = ...
def main(): Unit \ IO = ...

Note: Always use \ (backslash) for effects, never &.

No do Keyword for Effect Operations

Older versions of Flix required the do keyword to call an effect operation. This is no longer the case. Effect operations are called like regular functions using the Effect.operation() syntax.

Old (no longer valid):

eff DivByZero {
    def divByZero(): Void
}

def divide(x: Int32, y: Int32): Int32 \ DivByZero =
    if (y == 0) do DivByZero.divByZero() else x / y // Wrong -- Outdated

Current (correct, as of Flix 0.68.0):

eff DivByZero {
    def divByZero(): Void
}

def divide(x: Int32, y: Int32): Int32 \ DivByZero =
    if (y == 0) DivByZero.divByZero() else x / y

Note: Simply call DivByZero.divByZero() without the do keyword.

Effect Handler Syntax: run/with handler

Older versions of Flix used run { ... } with Effect { ... } to handle effects. The current syntax requires the handler keyword: run { ... } with handler Effect { ... }.

Old (no longer valid):

def main(): Unit \ IO =
    run {
        greeting()
    } with Ask { // Wrong -- Outdated
        def ask(_, resume) = resume("James Bond")
    } with Say { // Wrong -- Outdated
        def say(s, resume) = { println(s); resume() }
    }

Current (correct, as of Flix 0.68.0):

def main(): Unit \ IO =
    run {
        greeting()
    } with handler Ask {
        def ask(_, k) = k("James Bond")
    } with handler Say {
        def say(s, k) = { println(s); k() }
    }

Note: Always write with handler EffectName, not just with EffectName. Multiple handlers are chained: with handler A { ... } with handler B { ... }.

Java Types Must Be Imported

In Flix, Java classes must always be imported before they can be used. You cannot use fully-qualified Java class names inline. Use import declarations at the top of your file or module.

Old (no longer valid):

def main(): Unit \ IO =
    let f = new java.io.File("foo.txt"); // Wrong -- Outdated
    println(f.getName())

Current (correct, as of Flix 0.68.0):

import java.io.File

def main(): Unit \ IO =
    let f = new File("foo.txt");
    println(f.getName())

Note: Always import the class first, then use its short name.

No Old-Style import for Java Methods

Older versions of Flix used a special import syntax inside function bodies to access Java constructors, methods, and static methods. This syntax no longer exists. Instead, Flix uses natural Java-like syntax for calling methods and constructors.

Old (no longer valid):

def area(w: Int32, h: Int32): Int32 =
    import static java.lang.Math.abs(Int32): Int32 \ {}; // Wrong -- Outdated
    abs(w * h)

Current (correct, as of Flix 0.68.0):

import java.lang.Math

def area(w: Int32, h: Int32): Int32 =
    unsafe Math.abs(w * h)

Similarly, object methods are called with regular dot syntax:

Old (no longer valid):

def getLength(f: ##java.io.File): Int64 =        // Wrong -- Outdated
    import java.io.File.length(): Int64 \ {};     // Wrong -- Outdated
    length(f)

Current (correct, as of Flix 0.68.0):

import java.io.File

def getLength(f: File): Int64 =
    unsafe f.length()

Note: Import the class at the top level and then call methods with standard dot syntax. Use unsafe blocks only when you know the Java method is pure. All Java interop has the IO effect by default. See Calling Methods for more details.

Annotations Are Uppercase

Flix annotations use uppercase names.

Old (no longer valid):

@test                                    // Wrong -- Outdated
def testAdd01(): Bool = 1 + 2 == 3      // Wrong -- Outdated

Current (correct, as of Flix 0.68.0):

@Test
def testAdd01(): Unit \ Assert =
    Assert.assertEq(expected = 3, 1 + 2)

Note: Use @Test, not @test. Other annotations are similarly uppercase, e.g. @Parallel, @Lazy, @MustUse.

Datalog inject Requires Arity

Older versions of Flix allowed inject without specifying the arity of the predicate. The current syntax requires the arity using the Predicate/N notation.

Old (no longer valid):

let edges = inject s into Edge; // Wrong -- Outdated

Current (correct, as of Flix 0.68.0):

let edges = inject s into Edge/2;

The general form is Predicate/Arity. When injecting multiple collections, each predicate requires its arity:

let p = inject names, jedis into Name/1, Jedi/1;

No rel or lat Declarations for Datalog

Older versions of Flix required explicit rel and lat declarations to introduce predicate symbols for Datalog constraints. This is no longer the case. Predicate symbols are inferred from use and do not need to be declared.

Old (no longer valid):

rel Edge(x: Int32, y: Int32) // Wrong -- Outdated
rel Path(x: Int32, y: Int32) // Wrong -- Outdated

Current (correct, as of Flix 0.68.0):

Predicate symbols like Edge and Path are simply used directly in Datalog rules and facts without any declaration:

def reachable(s: Set[(Int32, Int32)], src: Int32, dst: Int32): Bool =
    let rules = #{
        Path(x, y) :- Edge(x, y).
        Path(x, z) :- Path(x, y), Edge(y, z).
    };
    let edges = inject s into Edge/2;
    let paths = query edges, rules select true from Path(src, dst);
    not (paths |> Vector.isEmpty)

Note: The predicate symbols Edge and Path do not have to be explicitly introduced; they are simply used. Similarly, for lattice semantics, no lat declaration is needed.