Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Flix – Safe, reliable, concise, and functional-first programming language (flix.dev)
213 points by nikolay on May 23, 2022 | hide | past | favorite | 42 comments


The Datalog support is a very interesting feature for such a fully-featured language. From the home page:

    def reachable(g: List[(String, Int32, String)], minSpeed: Int32): List[(String, String)] =
        let facts = project g into Road; 
        let rules = #{
            Path(x, y) :- Road(x, maxSpeed, y), if maxSpeed >= minSpeed.
            Path(x, z) :- Path(x, y), Road(y, maxSpeed, z), if maxSpeed >= minSpeed.
        };
        query facts, rules select (src, dst) from Path(src, dst) |> Foldable.toList
Composing datalog natively like any other type – very cool.


> Composing datalog natively

I'm naive. What does this mean and why is it important?


Datalog programs are values. You can store them in local variables, pass them as arguments, and return them. If you have two Datalog values you can combine them into one (effectively it's just the union of them). This allows you to write small "Datalog fragments" and stitch them together into a larger program. The type system ensures that this stays meaningful.

(I am one of the authors of Flix.)


Datalog is a logic programming language for relational data. It is much easier to express recursive relationships in Datalog compared to SQL. The most obvious application of Datalog is for working with graphs. The starter example I've usually seen is "graph reachability", which something like "given a list of edges between nodes, tell me if I can reach node X from node Y".

    // Given an edge x -> y, there is a path x -> y.
    path(x, y) :- edge(x, y).

    // Given an edge x -> z and a path z -> y, there is a path x -> y.
    path(x, y) :- edge(x, y: z), path(x: z, y).
Here is the equivalent recursive SQL (SQLite syntax):

    WITH RECURSIVE path(x, y) AS (
      SELECT
        edge.x AS x,
        edge.y AS y
      FROM
        edge
      UNION
      SELECT
        edge.x AS x,
        path.y AS y
      FROM
        edge,
        path
      WHERE
        edge.y = path.x
    )
    SELECT
      DISTINCT *
    FROM
      path

If you want to play with Datalog, https://percival.ink is an online notebook with a very gentle introduction. I also published a version of percival.ink that converts simple Datalog queries to recursive SQLite queries here: https://percival.jake.tl (the example above is from percival.jake.tl).

As jorkadeen said in their answer (I added emphasis to the last sentence):

> Datalog programs are values. You can store them in local variables, pass them as arguments, and return them. If you have two Datalog values you can combine them into one (effectively it's just the union of them). This allows you to write small "Datalog fragments" and stitch them together into a larger program. The type system ensures that this stays meaningful.

Why this is important: From my perspective is that a very large percent of industrial code interacts with data (in databases) by building query strings. In most languages, we throw away all the nice features of the language when it comes to data access. To regain some level of composability and safety, developers pour years into libraries like [Arel in Ruby](https://www.rubydoc.info/gems/arel) or [Calcite in Java](https://calcite.apache.org/docs/algebra.html). The most notable example of language native database access is [Language Integrated Query (LINQ)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...) in C#. But all of these tools are hampered by the constraints of SQL. SQL is very good for flat, normalized data with one or two joins. Once you begin working with trees or graphs, tools based around SQL become cumbersome.

Using datalog instead of SQL means you have a logical langauge that is inherently more accepting of composition in a theoretical way. But, most systems that speak datalog today are extremely specialized. [Souffle](https://souffle-lang.github.io/simple) and [differential-datalog](https://github.com/vmware/differential-datalog) are useful ahead-of-time compilers for datalog, but you have to design your whole program around using them. The datalog variant [inside Datomic](https://docs.datomic.com/on-prem/query/query.html) is dynamic, but is only usable for accessing data in a data store. Good luck using it for analysis of data in memory.

Including datalog into the language as first-class values mean that you can access the power of datalog and actually compose it in practice.

[Note: I've never used Flix – just a datalog fan.]


in prolog the definition of path is something like: path(x,y) := edge(x,y). path(x,z) := edge(x,y),path(y,z).

So to me it seems a little strange the definition of path in datalog from a prolog user point of view.


At first sight, in the examples: inc1(7) pure, inc2(8) impure, what's the meaning of inc2? (some kind of increment?) Then in the definion of map there is an argument function f, and in the definition there is a new function g that comes from nowhere, so for me at first sight the examples are not insightful.


In short you work with tree (AST) and can compose instead of stiching strings (builder pattern)

Good thing of having common AST in language is libs interoperability.

It’s like normal lanugages had SQL support built-in (except you can’t do that easily as SQL is order dependent)


Just look at any template heavy c++ library.


That really doesn't answer the question. (I'm not sure what it really means either, it's a good question.)


Related:

In Defense of Programming Languages - https://news.ycombinator.com/item?id=27799063 - July 2021 (1 comment)

Flix – Next-generation reliable, concise, functional-first programming language - https://news.ycombinator.com/item?id=25513397 - Dec 2020 (84 comments)

The Flix Programming Language - https://news.ycombinator.com/item?id=19928153 - May 2019 (2 comments)


Sadly the effects system seems to be just "pure vs impure" tracking per function. No arbitrary effects or custom handlers.

Is that accurate?I was hoping for something more like Koka.

Otherwise that is a very intriguing combination of features, especially first class Datalog constraints.

Any plans for additional backends? (native, Webassembly)?


At the moment the effect system only distinguishes between pure and impure (and effect polymorphic) expressions, but we are working towards a richer system. For example, we will soon be able to express something like:

  def swap!(a: Array[a, r], i: Int32, j: Int32): Unit \ { Read(r), Write(r) }
We are aware of algebraic effects and handlers, but let's just say that we prefer to underpromise and overdeliver.

(I am one of the authors of Flix.)


As someone who loves Haskell a lot and was looking for a functional language on the JVM, the language looks absolutely awesome. If the richer effect system gets implemented this has a shot to becoming a staple of mine! I'm reading the documentation right now, and I had a few questions:

- is there any plans to add restricted forms of instance overlapping, in the spirit of purescript's instance chains (or Haskell's overlappable/overlapping)?

- I couldn't find resources on error handling, does flix use some sort of Either/Result type? and if so, how ergonomic is it? I'm assuming Flix doesn't have special syntax like Scala/Haskell for monadic for(/do) comprehensions, so I fear that Either based error handling would quickly become gnarly with just monadic operators. Is there any example of Result-dense code out there to check out? I couldn't find it in the example (admittedly, I have checked quite thoroughly yet)

- I read somewhere on the docs (I think on the design flaws blog?) that Flix at one point had UFCS, is that still supported? I can't seem to make it work on the playground

- Is there anywhere I can read up on the way instance resolution works? More specifically, I'm interested in whether Flix takes only instance heads or it also takes the constraints into account when choosing an instance (if it does take constraints into account, that makes overlapping instances a lot less useful in a good way, I think?)

In any case, great work on the language! I will for sure try this out the first time I get the chance, it looks great and has basically 99% of the features that I would ever want in a programming language. Keep up the good work!


Thanks for the kind words! To try to answer your questions:

- We are debating whether to allow a limited form of overlapping instances. No decision has been made yet.

- Flix uses the Options and Errors. Flix also has monadic let* making it easier to work with these two types.

- UFCS had to be removed because it caused unavoidable ambiguities with records :(

- I am not sure I understand the question about instance resolution. Feel free to stop by our Gitter channel to discuss further.


Thanks for the response! I will surely hop on the gitter when I have some time. I'm also really happy about the presence of let*. I guess I have something to try this evening after work :)


Is this what I think it is?

Ref. _What color is your function_


The color of your function can be "effect polymorphic". In other words, e.g. List.map works with both pure and impure functions. You don't have to write two versions of List.map.


No, effect systems aren't a different function type, in fact you could mitigate the whole function color mess in $lang with an effect system.


Any resources to learn effects for a novice in Haskell?


Many languages running on JVM boast of an FFI allowing use of much existing code already written in Java. Is such, or will such be, possible in Flix?

What is the overall status of the implementation, particularly for real world applications (features like talking to a network, file systems, databases and operating system, and/or FFIs for JVM or non-JVM languages)?


I found this[1] section in the documentation on interoperability within the JVM. It looks fairly straightforward to work with. I'm not sure about FFI more generally.

[1] https://doc.flix.dev/interoperability/


> Flix looks quite similar to Scala. How are the two languages related?

> Flix borrows a lot of syntax from Scala, hence the two languages have a similar feel. We think Scala made many good design choices with respect to syntax, including: (a) the use of short keywords, (b) the x : T syntax for type annotations, (c) the List[Int32] syntax for type parameters, and (d) if, match, etc. as expressions.

> Other than syntax, the two languages are very different: Scala is object-oriented, Flix is not. Scala has sub-typing, Flix does not. The Scala type system is unsound and has imperfect type inference, whereas the Flix type system is both sound and supports type inference.

https://flix.dev/faq/


This is an extremely strong set of features in one language. All the best in this project!


Wow, this looks pretty exciting. Like @jitl said, the idea of built-in Datalog support sounds really interesting. I don't know how I missed the earlier announcements of this, but now that I found it I'm definitely going to give it a spin.


> Flix – Safe, Reliable, Concise, and Functional-First Programming Language

"Ensure that you have at least Java 11 installed. "

How about a compiler or an interpreter ?

"Download the latest version of the Flix compiler (flix.jar) at https://github.com/flix/flix/releases/latest.

Create an empty folder (e.g. mkdir flixproject) and place the downloaded Flix JAR (flix.jar) into that folder.

Enter the created directory (e.g. cd flixproject) and run java -jar flix.jar init to create an empty Flix project.

Run java -jar flix.jar run to compile and run the project.

Flix will compile the project and execute the main function (located in src/Main.flix).

"

So getting started is about running an empty project file ? How about some code examples ? I wanted to see what is a "Safe, Reliable, Concise, and Functional-First Programming Language". And all i get is an empty project ?

After reading some docs it looks like a lisp without parantheses and one has to have a good understanding of lambda functions to be able to read the documentation.


Scroll down! The homepage shows examples of ADTs, purity tracking (yay!) and pattern matching on purity (oooooh), effect polymorphism (oooooh again), type classes, HKTs, and an embedded Datalog DSL.


I rather liked Nim's one line install of a binary. Starting and running a project is as simple as "nim c -r <file_name>.nim".

I appreciate languages/toolkits that are concise in their tooling. Golang is another language like this. The tooling is simple (though Golang is more opinionated than Nim in things like testing and project layouts) and easy to reason about.


Be sure to check out https://flix.dev/ and https://github.com/flix/flix/tree/master/examples for more examples.

(I am one of the authors of Flix.)


So great to see a language with ML heritage. Really nice feature set.


I've had my eye on flix for a while. I really like the feature set.


This looks amazing! Can’t wait to try this with Graal


So great to see a language with ML heritage. Really nice feature set.

Plus I really like the code example right there on the front page.


Looks interesting, will definitely dig into. Right off the bat though, I see 3 different syntaxes for typing?

> case Rectangle(Int32, Int32)

> def origin(): (Int32, Int32) =

> def twoByFour(): {w :: Int32, h :: Int32} =

case Rectangle: (Int32, Int32)

def origin(): (Int32, Int32)

def twoByFour(): (w: Int32, h: Int32)

?


The first one is part of the syntax for defining a sum type, the second is a function returning a tuple, and the third is a function returning a record.


I get that I just don’t understand why they chose to make them all so different.


Are you asking why different things look different?


Come on. I’m asking why they chose such different syntaxes for type annotations when it would not have been terribly challenging to keep them consistent. I even gave examples.

Parens vs curly braces. Single : vs double ::.


The first example isn't a type annotation, it's a type definition. The curly braces are consistent with the literal syntax for records. I agree that the use of double colons in record types seems inconsistent. I assume it resolves some ambiguity.


You are spot on. Its because we reserve `:` for type and kind ascriptions. For example, `42 : Int` or `true : Bool`. But we also have `Bool : Type`, i.e. `true : Bool : Type`. While kinds are not that common in every day code, we aim for consistency with ascriptions rather than with records. Hence we had to use a different symbol in record types.


> I even gave examples.

Yes, of different things that look different, then asked why.


Love the FAQ page!


wow this is a really pretty language, nice work to the authors. definitely will be giving it a shot




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: