Flix, an open-source programming language inspired by many programming languages, enables developers to write code in a functional, imperative or logic style. Flix looks like Scala, uses a type system based on Hindley-Milner and a concurrency model inspired by Go. The JVM language supports unique features such as the polymorphic effect system and Datalog constraints.
The community develops the language based on a set of principles such as: no null value, private by default, no reflection, separation of pure and impure code, correctness over performance and no global state.
main function is considered impure as the
println function has a side effect. The Flix compiler keeps track of the purity of each expression and guarantees that a pure expression doesn’t have side effects.
def main(_args: Array[String]): Int32 & Impure = println("Hello world!"); 0 // exit code
The Flix language supports Polymorphic Effects making it possible to distinguish between pure functional programs and programs with side effects. Functions are pure by default, or can explicitly be marked as pure:
def add(x: Int32, y: Int32): Int32 & Pure = x + y;
Functions with side effects can be marked explicitly as impure:
def main(_args: Array[String]): Int32 & Impure = println(add(21, 21)); 0 // exit code
The compiler displays an error
Impure function declared as pure whenever side effects are used in an explicitly marked Pure function:
def add(x: Int32, y: Int32): Int32 & Pure = x + y; println("Hello")
The separation of pure and impure code allows developers to reason about pure functions as if they are mathematical functions without side effects.
Datalog, a declarative logic programming language, may be seen as a query language such as SQL, but more advanced. Flix supports Datalog as a first-class citizen making it possible to use Datalog constraints as function arguments, returned from functions and stored in data structures. Flix may be used with Datalog to express fixpoint problems such as determining the ancestors:
def getParents(): # ParentOf(String, String) = # ParentOf("Mother", "GrandMother"). ParentOf("Granddaughter", "Mother"). ParentOf("Grandson", "Mother"). def withAncestors(): # ParentOf(String, String), AncestorOf(String, String) = # AncestorOf(x, y) :- ParentOf(x, y). AncestorOf(x, z) :- AncestorOf(x, y), AncestorOf(y, z). def main(_args: Array[String]): Int32 & Impure = query getParents(), withAncestors() select (x, y) from AncestorOf(x, y) |> println; 0
This displays the following results:
[(Granddaughter, GrandMother), (Granddaughter, Mother), (Grandson, GrandMother), (Grandson, Mother), (Mother, GrandMother)]
Flix provides built-in support for tuples and records as well as algebraic data types and pattern matching:
enum Shape case Circle(Int32), // circle radius case Square(Int32), // side length case Rectangle(Int32, Int32) // height and width def area(s: Shape): Int32 = match s case Circle(r) => 3 * (r * r) case Square(w) => w * w case Rectangle(h, w) => h * w def main(_args: Array[String]): Int32 & Impure = println(area(Rectangle(2, 4))); 0 // exit code
InfoQ spoke to Magnus Madsen, assistant professor at the Department of Computer Science, Aarhus University in Denmark, and lead designer / developer of the Flix programming language.
InfoQ: What was the inspiration to create Flix?
Magnus Madsen: In the landscape of programming languages, I felt something was missing: a primarily functional language but with full support for imperative programming and with a twist of logic programming. A language at a unique point in the design space with the best ideas from OCaml, Haskell, and Scala.
I wanted a new language based on principles; clearly expressed ideas and design goals, ot just a mishmash of features. For me, a language should not try to be everything for everyone. Better to focus on a few core ideas and excel at those. Personally, I wanted a language that would be a joy to use with powerful features from functional programming: algebraic data types, pattern matching, and higher-order functions.
InfoQ: To what extent is Flix inspired by Scala?
Madsen: Flix draws a lot of inspiration from Haskell, OCaml, and Scala. We want to build on the solid ideas of these languages and go beyond them. The Flix syntax is inspired by Scala, i.e., it is based on keywords and braces, and will feel instantly familiar to many Scala programmers. The Flix type system is based on Hindley-Milner, the same expressive type system that powers OCaml and Haskell. Flix has type inference and will always infer the correct type within a function. While Scala has classes and objects, Flix has type classes borrowed from Haskell. Scala also has a form of type classes based on implicits. The type classes in Flix are closer to those in Haskell and integrate better with type inference.
InfoQ: Why create a new language rather than add capabilities to Scala?
Madsen: Scala is an excellent programming language that has pushed the boundaries of functional and object-oriented programming while maintaining great interoperability with Java.
With Flix, I wanted to go in a different direction and to start from a clean slate. In my experience, functional programming in Scala sometimes feels cumbersome due to limitations of Scala’s type inference. More importantly, I have become more and more convinced that object-oriented programming is a step in the wrong direction. In particular, I feel that the classic features (or values) of OOP – object identity, mutable state, and class inheritance – are not the right way to structure and think about programs.
For these reasons, Flix is not object-oriented. Nevertheless, Flix still supports many features programmers associate with OOP, including imperative programming with mutable data and records.
InfoQ: What are the most significant language features that you think Flix adds?
Madsen: We are actively working on Flix, but today we can identify three unique features that no other programming language currently has:
Flix has a type and effect system that separates pure and impure code. In Flix, every expression is either pure, impure, or effect polymorphic. A pure expression cannot have any side-effects and its evaluation always returns the same result. This is an ironclad guarantee enforced by the compiler. An impure expression may have side-effects. Finally, and most interestingly, for some expressions their purity depends on the purity of other expressions. For example, in a call to
List.map, the overall expression is pure if the function argument to
List.mapis pure. We say that such functions are considered effect polymorphic.
A Flix programmer (but typically a Flix library author) can use the type and effect system to write functions that are effect-aware. This is a type of meta-programming that enables a higher-order function (e.g.
Set.count) to reflect on the purity of its function argument(s). This knowledge enables support for automatic lazy or parallel evaluation. For example, in the Flix standard library, the
Set.countfunction runs in parallel when passed a pure function, but runs sequentially when passed an impure function; to preserve the order of effects and to avoid any thread safety issues.
Flix has support for first-class Datalog constraints. Datalog is a logic programming language, but can be thought of as a query language like SQL – but on steroids. Any SQL query is trivial to express in Datalog, but Datalog also makes it possible to write more complex queries on graphs. For example, a two-line Datalog program can be used to compute the shortest distance between two cities in a road network. Unlike some ORMs, the Datalog integration is seamless. There is no messy construction of strings or mapping of data types. Datalog queries use the same underlying data types as Flix. Finally, Datalog should not be used blindly, but for some computational problems, it is really a super-weapon.
InfoQ: For which type of applications do you think Flix is especially suited?
Madsen: We are designing Flix as a general-purpose programming language. Flix can be used where you would use Java, Kotlin, Scala, OCaml, or Haskell. We are taking a “batteries included” approach and the Flix standard library is now more than 30,000 lines of code with more than 2,000 functions. If the standard library lacks functionality, a programmer can always fall back on the Java library and/or on external JARs. We care a lot about correctness and stability. In particular, the Flix compiler and standard library have more than 12,500 manually written unit tests. Flix also comes with API documentation, a website, a “Programming Flix” manual, and a Visual Studio Code extension that implements most of the Language Server Protocol. We also have a nascent package manager.
InfoQ: What are your immediate goals for Flix – how big is the community currently?
Madsen: We are at a point where the language itself, the compiler, the standard library, and the documentation are ready for early adopters. We want software developers to start using Flix and we are ready to offer help via our Gitter channel. We are interested in feedback on the design to iterate and move forward. Flix is already used in production by a few companies. We would like to dramatically increase this number.
Now there are about 5-8 programmers actively working on Flix. A couple of programming language researchers, a PhD student, two student programmers, and several open-source contributors.
The Flix project is about a bit more than six years old. Most activity has happened in the last two years. The Flix compiler project consists of approximately 170,000 lines of code contributed by more than 40+ developers.
InfoQ: What language feature of Flix do you like most?
Madsen: Personally, I really like programming with algebraic data types, pattern matching, and functions. Flix’s concise syntax and type inference make it a breeze to write programs with these features. I find it convenient that, when needed, I can write messy imperative code without comprising the functional purity of the rest of my program. The effect system helps me structure my code into pure and impure parts. Usually, the core computation is pure whereas everything around it deals with the external world (e.g., files). I like that the type signature tells me almost everything I need to know about a function: the type of its arguments and return type, the type class instances that are required, and whether the function is pure or impure. Finally, while the Visual Studio Code extension is not yet perfect, it is still pleasant to use because it uses the real Flix compiler, i.e., I can trust that if VSCode says a program is error-free then the compiler agrees.
InfoQ: What part of Flix would you like to improve?
Madsen: In the last year we have made great progress on polishing the language, the compiler, and the standard library. Flix is ready for use, but there are still a few areas we would like to improve:
We hope to extend the Flix type and effect system to allow functions to use local mutation while remaining pure. For example, you could imagine a sorting function that internally uses a mutable array for in-place sorting, but to the outside appears as a pure function.
We want to extend the standard library with support for file I/O, interacting with the operating system, networking, and parsing (e.g., JSON) without having to rely on Java libraries.
We want to improve interoperability with Java without too many sacrifices.
InfoQ: Can you already tell us something about future directions for Flix?
Madsen: We want to under-promise and over-deliver. We prefer to talk about features that exist today and are ready for use. That said, Flix has recently received funding from Amazon Science and from DIREC for work on increasing the expressive power of type and effect systems, so that may offer a clue.