48 comments

[ 2.7 ms ] story [ 76.3 ms ] thread
I'd love to use Flix for something. It looks beautiful. But I'm a web dev, and never touch the JVM ecosystem
JVM is a no-starter. The language looks nice tho, shame they built it on JVM.
>> Flix is a principled effect-oriented functional, imperative, and logic programming language...

>> Why Effects? Effect systems represent the next major evolution in statically typed programming languages. By explicitly modeling side effects, effect-oriented programming enforces modularity and helps program reasoning.

Since when do side effects and functional programming go together?

On a language semantics note: the semantics of extending/restricting polymorphic records seem to follow Leijen's approach [0] with scoped labels. That is, if you have a record e.g. r1 = { color = "yellow" }, you can extend it with r2 = { +color = "red" | r1 }, and doing r2#color will evaluate to "red"... and if you then strip the field "color" away, r3 = { -color | r2 }, then you'll get back an original record, r3#color will evaluate to "yellow". Which IMO is the sanest approach, as opposed to earlier attempts of trying to outlaw such behaviour, preferably statically (yes, people developed astonishingly high-kinded type systems to track records' labels, just to make sure that two fields with the same label couldn't be re-added to a record).

[0] https://www.cs.ioc.ee/tfp-icfp-gpce05/tfp-proc/21num.pdf

Awesome, it even supports HKTs.

Can't find any mentions of typeclasses though, are they supported?

Give me typeclasses and macros comparable with Scala ones and I would be happy to port my libraries (distage, izumi-reflect, BIO) to Flix and consider moving to it from Scala :3

UPD: ah, alright, they call typeclasses traits. What about macros?

UPD2: ergh, they don't support nominal inheritance even in the most harmless form of Scala traits. Typeclasses are not a replacement for interfaces, an extremely important abstraction is missing from the language (due to H-M typer perhaps), so a lot of useful things are just impossible there (or would look ugly).

I like it. This is the first time Ive seen a new programming language demo that made me want to use it
I am deeply impressed by the depth and breadth of this language. Algebraic data types, logic programming, mutability, all there from the get go.

Another aspect that I love from their comparison table is that a single executable is both the package manager, LSP and the compiler. As I understand, the language server for Haskell has/had to do a lot of dances and re implement things from ghc as a dance between the particular ghc version and your cabal file. And maybe stack too, because I don't know which package manager is the blessed one these days. Not to shit on Haskell -- it is actually a very fine language.

However, the best feature is a bit buried and I wonder why.

How ergonomic is the integration with the rest of the JVM, from the likes of Java? AFAIK, types are erased by JVM compilers... With the concept of `regions` they have at least first class support for imperative interaction. Note: With the JVM you get billions worth of code from a high quality professional standard library, so that is a huge plus. That is why the JVM and .net core are IMHO the most sane choices for 90+% of projects. I think the only comparable language would be F#. I would love to see a document about Flix limitations in the JVM interoperability story.

__EDIT__

- There is a bit of info here. Basically all values from Flix/Java have to be boxed/unboxed. https://doc.flix.dev/interoperability.html

- Records are first-class citizens.

`case Circle(r) => 3 * (r * r)`

I, uh, think your math might need some checking :)

Anyone have a good primer on what effect-oriented programming looks like and how it’s used? Feel free to shill your own blog!

    // Computes the delivery date for each component.
    let r = query p select (c, d) from ReadyDate(c; d)
facepalm. Select should always come last, not first, haven't we learned anything from the problems of SQL? LINQ got this right, so it should look like:

    query p from ReadyDate(c; d) select (c, d)
Very cool language otherwise.
Probably Elixir spoiled me but when I see

  enum Shape {
    case Circle(Int32),
    case Square(Int32),
    case Rectangle(Int32, Int32)
  }
  def area(s: Shape): Int32 = match s {
    case Circle(r)       => 3 * (r * r)
    case Square(w)       => w * w
    case Rectangle(h, w) => h * w
  }
I wonder why not this syntax:

  def area(s: Shape.Circle(r)) = { 3 * (r * r) }
  def area(s: Share.Square(w)) = { w * w }
  def area(s: Shape.Rectangle(h, w)) = { h * w }

  area(Shape.Rectangle(2, 4))
The Int32 or Int32, Int32 types are in the definition of Shape, so we can be DRY and spare us the chances to mismatch the types. We can also do without match/case, reuse the syntax of function definition and enumerate the matches in there. I think that it's called structural pattern matching.
Tangential, but I have a basic question: What makes Aarhus (mainly its university/techhub) a powerhouse for Programming Languages?

C++, C#/Typescript, Dart, etc all have strong roots in that one small area in Denmark.

In general, I am curious what makes some of these places very special (Delft, INRIA, etc)?

They aren't your 'typical' Ivy League/Oxbridge univ+techhubs.

Is it the water? Or something else? :)

Very similar to the koka language. I hope these effect systems become mainstream.
Very cool language. The standard library looks mostly sane, although it does have `def get(i: Int32, a: Array[a, r]): a \ r` which means that it must have some kind of runtime exception system. Not my cup of tea, but an understandable tradeoff
no, that's a region variable if i understand correctly, so closer to a rust lifetime
Just looking at the language myself, but it seems that it treats out-of-bounds array access as a non-recoverable bug and panics [1, 2], whilst map access returns Option [3]. Exceptions are a language construct only to enable Java compatibility and not recommended otherwise [4], but that's not to say you couldn't implement your own try/catch using the effect system. `r` is a region variable as the sibling comment says.

[1] https://doc.flix.dev/chains-and-vectors.html#vectors

[2] https://flix.dev/principles/ See also "Bugs are not recoverable errors"

[3] https://api.flix.dev/Map.html#def-get

[4] https://doc.flix.dev/exceptions.html

Any code agents work well with this or do we have to start thinking with our own brain again?

Seriously though, looks like a cool language and makes me sad that LLMs will probably inhibit the adoption of new languages, and wonder what we can do about it.

Minor nit: the semicolons! Especially in the yield examples, since there's no "return" on the last line, the disparity looks weird.

That said, I like how the syntax isn't overly functional, and not too different from what we see in mainstream languages. I'd be fine with either braces or indentation, but the semicolons have to go!

We plan to explore semicolon inference in the future; but there are a lot of dangerous corner cases to consider.
This language seems to be aiming high. Congrats!

Looking at their code however, I'm realizing one thing Elixir got "right", in my view, is the order of arguments in function calls.

For example, in Elixir to retrieve the value associated with a key in a map, you would write Map.get(map, key) or Map.get(map, key, default).

This feels so natural, particularly when you chain the operations using the pipe operator (|>):

  map
  |> Map.put(key, value)
  |> Map.get(key)
In Flix it seems one needs to write Map.get(x, map), Map.insert(x, y, map). I guess it follows in the footsteps of F#.
I think their DIDYOUKNOW.md file in the source code is worth showing in full, as it describes the language in a more compact form:

---

# Did You Know?

## Language

Did you know that:

- Flix offers a unique combination of features, including: algebraic data types and pattern matching, extensible records, type classes, higher-kinded types, polymorphic effects, and first-class Datalog constraints.

- Flix has no global state. Any state must be passed around explicitly.

- Flix is one language. There are no pragmas or compiler flags to enable or disable features.

- Flix supports type parameter elision. That is, polymorphic functions can be written without explicitly introducing their type parameters. For example, `def map(f: a -> b, l: List[a]): List[b]`.

- the Flix type and effect system can enforce that a function argument is pure.

- Flix supports effect polymorphism. For example, the `List.map` function is effect polymorphic: its purity depends on the purity of its function argument.

- in Flix every declaration is private by default.

- In Flix no execution happens before `main`. There is no global state nor any static field initializers.

- Flix supports full tail call elimination, i.e. tail calls do not grow the stack. Flix -- being on the JVM -- emulates tail calls until Project Loom arrives.

- Flix supports extensible records with row polymorphism.

- Flix supports string interpolation by default, e.g. "Hello ${name}". String interpolation uses the `ToString` type class.

- Flix supports the "pipeline" operator `|>` and the Flix standard library is designed around it.

- In Flix type variables are lowercase and types are uppercase.

- In Flix local variables and functions are lowercase whereas enum constructors are uppercase.

- Flix supports set and map literals `Set#{1, 2, 3}` and `Map#{1 => 2, 3 => 4}`.

- Flix supports monadic do-notation with the `let*` construct.

- Flix supports "program holes" written as either `???` or as `?name`.

- Flix supports infix function applications via backticks.

- Flix compiles to JVM bytecode and runs on the Java Virtual Machine.

- Flix supports channel and process-based concurrency, including the powerful `select` expression.

- Flix supports first-class Datalog constraints, i.e. Datalog program fragments are values that can be passed to and returned from functions, etc.

- Flix supports compile-time checked stratified negation.

- Flix supports partial application, i.e. a function can be called with fewer arguments that its declared formal parameters.

- the Flix type and effect system is powered by Hindley-Milner. The same core type system that is used by OCaml, Standard ML, and Haskell.

- the Flix type and effect system is sound, i.e. if a program type checks then a type error cannot occur at run-time. If an expression is pure then it cannot have a side-effect.

- the Flix type and effect system supports complete type inference, i.e. if a program is typeable then the type inference with find the typing.

- The Flix "Tips and Tricks"-section https://doc.flix.dev/tipstricks/ describes many useful smaller features of the language.

- Flix has a unique meta-programming feature that allows a higher-order functions to inspect the purity of its function argument(s).

- Flix names its floats and integers types after their sizes, e.g. `Float32`, `Float64`, `Int32` and `Int64`.

- Flix -- by design -- uses records for labelled arguments. Records are a natural part of the type system and works for top-level, local, and first-class functions.

- Flix -- by design -- has no implicit coercions, but provide several functions for explicit coercions.

- Flix -- by design -- disallows unused variables and shadowed variables since these are a frequent source of bugs.

- Flix -- by design -- disallows allow unused declarations. This pre...

Curious if anyone can weigh in on why Flix requires a developer to explicitly mark a function as pure. I'd imagine in almost all cases this can be derived through static analysis.