Flix is a functional, imperative, and logic programming language developed at Aarhus University, with funding from the Independent Research Fund Denmark, and by a community of open source contributors. The Flix language supports algebraic data types, pattern matching, parametric polymorphism, currying, higher-order functions, extensible records, channel and process-based concurrency, and tail call elimination. Two notable features of Flix are its type and effect system and its support for first-class Datalog constraints.
The Flix type and effect system supports Hindley-Milner-style type inference. The system separates pure and impure code: if an expression is typed as pure then it cannot produce an effect at run-time. Higher-order functions can enforce that they are given pure (or impure) function arguments. The type and effect system supports effect polymorphism which means that the effect of a higher-order function may depend on the effect(s) of its argument(s).
Flix supports Datalog programs as first-class values. A Datalog program value, i.e. a collection of Datalog facts and rules, can be passed to and returned from functions, stored in data structures, and composed with other Datalog program values. The minimal model of a Datalog program value can be computed and is itself a Datalog program value. In this way, Flix can be viewed as a meta programming language for Datalog. Flix supports stratified negation and the Flix compiler ensures stratification at compile-time. Flix also supports an enriched form of Datalog constraints where predicates are given lattice semantics.
Overview
Flix is a programming language in the ML-family of languages. Its type and effect system is based on Hindley-Milner with several extensions, including row polymorphism and Boolean unification. The syntax of Flix is inspired by Scala and uses short keywords and curly braces. Flix supports uniform function call syntax which allows a function call f(x, y, z) to be written as x.f(y, z). The concurrency model of Flix is inspired by Go and based on channels and processes. A process is a light-weight thread that does not share (mutable) memory with another process. Processes communicate over channels which are bounded or unbounded queues of immutable messages.
While many programming languages support a mixture of functional and imperative programming, the Flix type and effect system tracks the purity of every expression making it possible to write parts of a Flix program in a purely functional style with purity enforced by the effect system.
Flix programs compile to JVM bytecode and are executable on the Java Virtual Machine (JVM). The Flix compiler performs whole program compilation, eliminates polymorphism via monomorphization, and uses tree shaking to remove unreachable code. Monomorphization avoids boxing of primitive values at the cost of longer compilation times and larger executable binaries. Flix has some support for interoperability with programs written in Java.
Flix supports tail call elimination which ensures that function calls in tail position never consume stack space and hence cannot cause the call stack to overflow. Since the JVM instruction set lacks explicit support for tail calls, such calls are emulated using a form of reusable stack frames. Support for tail call elimination is important since all iteration in Flix is expressed through recursion.
The Flix compiler disallows most forms of unused or redundant code, including: unused local variables, unused functions, unused formal parameters, unused type parameters, and unused type declarations, such unused constructs are reported as compiler errors. Variable shadowing is also disallowed. The stated rationale is that unused or redundant code is often correlated with erroneous code
A Visual Studio Code extension for Flix is available. The extension is based on the Language Server Protocol, a common interface between IDEs and compilers being developed by Microsoft.
Flix is open source software available under the Apache 2.0 License.
Examples
Hello world
The following program prints "Hello World!" when compiled and executed:
def main(): Unit & Impure =
Console.printLine("Hello World!")
The type and effect signature of the main function specifies that it has no parameters, returns a value of type Unit, and that the function is impure. The main function is impure because it invokes printLine which is impure.
Algebraic data types and pattern matching
The following program fragment declares an algebraic data type (ADT) named Shape: