Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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).



Flix supports type classes (called "traits") with higher-kinded types (HKTs) and with associated types and associated effects. A Flix trait can provide a default implementation of a function, but specific trait instances can override that implementation. However, Flix has no inheritance. The upshot is that traits are a compile-time construct that is fully eliminated through monomorphization. Consequently, traits incur no runtime overhead. Even better, the Flix inliner can "see through" traits, hence aggressive closure elimination is often possible. For example, typical usage of higher-order functions or pipelining is reduced to plain loops at the bytecode level without any closure allocation or indirection.

Flix does not yet have macros-- and we are afraid to add them due to their real (or perceived) (ab)use in other programming languages.

We are actively looking for library authors and if you are interested, you are more than welcome to stop by our Gitter channel.


> The upshot is that traits are a compile-time construct that is fully eliminated through monomorphization.

So, apparently, I can't re-implement distage for Flix.

I don't mind a little bit of overhead in exchange for a massive productivity boost. I don't even need full nominal inheritance, just literally one level of interface inheritance with dynamic dispatching :(

> their real (or perceived) (ab)use in other programming languages.

Without macros I can't re-implement things like logstage (effortless structured logging extracting context from AST) and izumi-reflect (compile-time refleciton with tiny runtime scala typer simulator).


The reality is that careless programmers will do bad things with any tool they happen to pick up. Using that as an excuse to reduce the power of a tool is poor form.

Another way of putting it is to point out that removing goto from a language isn't going to reduce the occurrence of spaghetti code. The average skill and care of the developers who happen to be using that language is what does that.


Not sure I agree. A simple example: If your language has null as a subtype of every type then you will have null ptr exceptions everywhere. If your language does not have a null value then you won't. The situation is not as clear cut as you suggest.

Yes, you can write spaghetti code in any language. But a good language design can help (a) reduce errors and (b) nudge the developer towards writing better code.


I feel like that example doesn't fit except in the specific case that the language designer restricts the usage of null in a way that reduces the overall expressiveness and power of the language. Whereas (but one example) the operators provided by Kotlin don't do that.

Obviously how exactly you structure a particular end result is going to involve lots of fuzzy tradeoffs. My point wasn't about such nuance but rather the sort of reasoning that leads the the dismissal of an entire feature (in this case proper macros) on the basis of saving developers from themselves.

There should be (at least IMO) a clear delineation between a language design that makes it possible to do things in a sensible manner versus the realm of style guides, linters, and pre-commit hooks that enforce restrictions intended to maintain sanity on large projects. I shouldn't feel compelled due to deficiencies in the design of the language to reach for constructs like goto but those constructs should still be there if I have a need for them. People shouldn't feel compelled to waste time patching their tools to work around the opinions of the designers being forced on them. [1][2]

That said, it would be nice if compilers themselves universally provided native linting facilities, possibly even enabled by default.

[1] https://github.com/kstenerud/go

[2] https://github.com/tpope/heroku-fucking-console


I fully agree with you here.

I primarily write JVM applications these days, and my go-to is Kotlin.

Not because I think it's the "best" JVM language -- quite the opposite, I think Scala 3 is potentially the best-designed pragmatically useable language at the moment.

But Scala 3 gives you "too much rope to hang yourself with".

If you're the only person touching a codebase that's fine, but if you have to work with others I don't want to introduce the possibility of a bunch of implicit type classes, macros, and insane type definitions.

I'll take the reduced expressiveness of Kotlin for it's working-class philosophy and simpler mental model.


> But Scala 3 gives you "too much rope to hang yourself with".

No, you use it wrong way. It gives you capability to write cleanest code possible. As with any expressive language you have to select a subset of features and a specific style and maintain it.

Unmaintainable code can be written in any language, expressive ones provide you with tools to keep code maintainable.

HKTs and macros make possible things which are completely impossible in most other languages without a preprocessor/compiler plugin.


I don't have the mental energy to review every line of code and argue with co-workers that they're "using it the wrong way" unfortunately.

Maybe in my younger years, but not after the first decade...

This is why Rob Pike designed Go the way he did, I think.


> I don't have the mental energy to review every line of code and argue

You don't have to. Just your process is broken.

> first decade

I've been using Scala since 2008. I'm not a smart guy, so made some smart tools which do the enforcement job for me.


that's a cool story for 1 person projects that use no libraries.

also, no working on other people projects.

which is fine. i do clojure, i stay in my niche!


Not true, we successfully maintain more than 1 MLoC of Scala code. But our framework is completely homegrown.

> no libraries.

We made the cats->zio adapters and BIO, we do use libraries, in a sense more extensively than other teams out there do.


> Flix does not yet have macros-- and we are afraid to add them due to their real (or perceived) (ab)use in other programming languages.

I think the abuse is not that much of a problem. It's rather that it makes it much much harder to change the language later on because it will break macros (like it did between Scala 2 and 3, causing many people to be stuck on Scala 2 due to libraries using macros heavily).

If I might add a suggestion: add type providers to the language (like in F#). It solves a lot of the problems that macros are often used for, such as generating code from SQL DDLs, API specs, etc. (or vice versa).


Sorry to hijack, but since you are involved, can you explain why tail call optimization would incur a run time perf penalty, as the docs mention? I would expect tail call optimization to be a job for the compiler, not for the runtime.


We have to emulate tail calls using trampolines. This means that in some cases we have to represent stack frames as objects on the heap. Fortunately, in the common case where a recursive function simply calls itself in tail position, we can rewrite the call to a bytecode level loop and there is no overhead.


Thanks for explaining that term. That sounds really bad indeed. Maybe this is way too technical, but representing them as stack pointers was unfeasible?


The JVM (and other VMs for that matter) do not grant direct access to the stack.

But the good news is that the common case incurs no overhead.


TCO (tail call optimization) is often confused with TCE (tail call elimination), the latter is a runtime guarantee whereas the former is a compiler's best effort attempt to statically optimize tail calls.


Thanks! So you are implying that `TCO :: Maybe TCE`?

I am trying to think of a situation where a functional language compiler does not have enough information at compile time, especially when effects are witnessed by types.


I'm not a compiler dev, but I know that many functional programming languages struggle with this in the same manner if the target platform does not support TCE itself, and therefore require trampolining.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: