Remix.run Logo
Show HN: Fuse – statically typed functional programming language(fuselang.org)
86 points by the_unproven 9 hours ago | 19 comments

Hi HN! I've been working on the fuse programming language, it's a statically typed purely functional language with higher-kinder types and ad-hoc polymorphism. It compiles to the GRIN whole-program optimizer, producing LLVM-generated native code.

Fuse supports ADTs, Generics, Type Methods, Traits, Pattern matching etc. all in a functional style with no mutations.

I’ve been developing the language for 5 years, with code written in Scala. I’ve started coding the language from the base of System F that was implemented as part of the book: Types and Programming Languages (tapl). And then extending with concepts such as Bidirectional Type Checking with Higher-Rank Polymorphism.

I’ve mainly drawn inspiration from Rust, Haskell, Scala and Python (in terms of syntax). It all started because I wanted a language that has Rust-like concepts such as: ADT, Traits, Impl block syntax, etc. but have the pure functional semantics.

I'd would love feedback on the language design and its general usage.

deepsun 17 minutes ago | parent | next [-]

Off topic: what is the most convenient statically typed language that don't require compilation/transpilation? To run instead of bash or Python, but types are mandatory (not just hints like python).

I know jShell has been here like 10 years, but I'm not sure it's convenient to quickly write to a file, query a url, etc.

.ksh for Kotlin? Typescript (through Deno)? Lua?

codebje 7 hours ago | parent | prev | next [-]

It's nice to see a GRIN backend in the wild!

It looks like a tidy little functional language - a small, easily grasped syntax surface and generally clear semantics. That you've got it to the point that it can compile and run proper programs is a great achievement for a solo dev project!

The string type in the standard library isn't Unicode-aware, might be worth just noting that. Unicode support can be a big undertaking, but considering whether you'll add it later or not might affect your library design now.

I don't really understand why you have an IO monad. The language isn't pure - `.exec()` means any function can perform IO actions no matter its type signature - so what's IO really for?

Do `impl` additions export? What happens when two libraries add the same function name with different signatures (or just bodies!) to a type's `impl` ?

Is currying automatic? It doesn't seem to be, but, eg, the `sum(x: i32, y: i32)` function theoretically could be called as `sum(5)` to create a closure, but this isn't a documented feature if so.

The website's font is using ligatures, not unicode operator symbols - I'd personally find it much clearer to use a non-ligature font to show what's really there, but that's immaterial to the language.

the_unproven 6 hours ago | parent [-]

First of all thanks for all the feedback and looking into it, appreciate it!

Yeah GRIN is a great project, it took a lot of debugging and analysis to make it compile 100% especially with monomorphization involved.

I'll look into Unicode support, makes total sense. Didn't scope it in initially. I can fix the site ligatures too, that's a fair remark.

> I don't really understand why you have an IO monad. The language isn't pure - `.exec()` means any function can perform IO actions no matter its type signature - so what's IO really for?

That's a fair point, I still left a place for `.exec()` to happen as un-handled side-effect. But the preference is with using the IO monad as the stdlib is built around it, with `main() -> IO[i32]` as a type signature. As languages evolves I'm planning to build a runtime around IO execution, and build more constraints for handling strict side-effects. However for this initial stage of the language, I left it as a really simple solution.

> Do `impl` additions export? What happens when two libraries add the same function name with different signatures (or just bodies!) to a type's `impl` ?

For now the language doesn't support modules (libraries), I'm planning on adding it. At the moment it's a bit of undefined behavior, as overloading would occur with latest `impl` definition.

> Is currying automatic? It doesn't seem to be, but, eg, the `sum(x: i32, y: i32)` function theoretically could be called as `sum(5)` to create a closure, but this isn't a documented feature if so.

In the type-system it is automatic, and it successfully passes type checker as it's entirely built on top of lambda calculus. But there's an issue with codegen right now. I can def look into it and document it.

nxobject an hour ago | parent [-]

I’d really encourage you with Unicode support, as frustrating as of a side track it may be - I’ve had fun dogfooding my hobby languages by writing small web servers and CLI utilities. Even if it just generates part of your shell prompt!

Twey 5 hours ago | parent | prev | next [-]

Love to see a real-world example of GRIN!

    trait Functor[A]:
      fun map[B](self, f: A -> B) -> Self[B];
This looks a little wacky to me. I see that you can write HKTs in their η-long form and refer to them unapplied (`Functor`). But I don't understand how I would use this syntax to attach something to the trait that _doesn't_ depend on `A`. For (a silly) example,

    trait SizedFunctor[A]: Functor[A]:
      type Size;
      fun size(self) -> Size;
How do I know that `List[A]::Size` is the same type as `List[B]::Size`?

Relatedly, I want to read `Self` in there as ‘the thing that implements `Functor[A]`’ (e.g. List[A]`), but that makes `Self[B]`, instantiated, mean `List[A][B]`, which I think should be a kind error.

the_unproven 39 minutes ago | parent [-]

`Self` isn't the applied type (`List[A]`), rather it's the type constructor of kind `* -> *` constrained by `Functor`. In the map example it gets desugared into:

  fun map[Self: Functor, A, B](self: Self[A], f: A -> B) -> Self[B];
Since `Self` is the unapplied constructor, `Self[B]` just means `Functor[B]` e.g. `List[B]` not `List[A][B]`.

The example you've shown with `SizedFunctor` is not currently supported, as support for associated types is not yet implemented. I got it on the roadmap tho!

helix278 3 hours ago | parent | prev | next [-]

Superficially, I find the syntax very untuitive to read. Most languages opt to use <> for typevariables. Is there a specific reason you chose to deviate from that and use []?

dwb 2 hours ago | parent | next [-]

I actually can't think of any functional languages that use < > for type variables (I'm sure there's one or two, but it can't be common)

helix278 23 minutes ago | parent | next [-]

Depends on what you mean by functional language. I was considering main-stream languages that support some form of functional programming. So Rust, F#, C#, Kotlin, Swift, Typescript, etc

smt88 2 hours ago | parent | prev [-]

F# does, I think

smuffinator 2 hours ago | parent | prev [-]

This is only true for C-style languages? Flix uses square brackets, as does Effekt, and Nim, as well as Python. Probably many more I'm missing. Gleam even uses normal parens.

nee_oo_ru 5 hours ago | parent | prev | next [-]

Congrats on the release! Really cool to see a working GRIN backend. I'm definitely bookmarking this to give it a spin as soon as I get some spare time.

toplinesoftsys 2 hours ago | parent | prev | next [-]

Great project - clean and simple syntax, pure functional language, uses GRIN framework.

strong-self 5 hours ago | parent | prev | next [-]

found tree-sitter-fuse in the org, updated yesterday. is editor support next, like an lsp over the scala typechecker?

the_unproven 4 hours ago | parent [-]

Yeah the LSP support is next, my goal is to implement the language server in the fuse itself. At the moment there’s a simple formatter implementation fusefmt: https://github.com/fuselang/fuse/blob/master/examples/fusefm..., you can compile it with fuse and hook-it with your editor.

For example I’ve this config in helix:

  [[language]]                                                                                                                       
  name = "fuse"                                                                                                                      
  scope = "source.fuse"                                                                                                              
  file-types = ["fuse"]                                                                                                              
  injection-regex = "fuse"                                                                                                           
  comment-token = "#"                                                                                                                
  indent = { tab-width = 2, unit = "  " }                                                                                            
  auto-format = true                                                                                                                 
  formatter = { command = "fusefmt" }                                                                                                
                                                                                                                                     
  [[grammar]]                                                                                                                        
  name = "fuse"                                                                                                                      
  source = { git = "https://github.com/stevanmilic/tree-sitter-fuse", rev = "eb5698f4867a4192064e54a92be280f4d2130e03" }
qsera 7 hours ago | parent | prev | next [-]

I wish you took Haskell's syntax as such. Why did you mix rust and possibly other stuff with it?

the_unproven 5 hours ago | parent [-]

Haskell is a great language with a really advanced type-system, although I found its syntax hard to read at times especially as I was exploring the language at first. On the other hand I really liked how Rust syntax was defined in terms of ADTs, Traits & Methods Impls, with type signatures required for functions. Hence I wished for a similar functional language that has such write-style and type concepts, but stripping away the borrow checker, mutations, etc.

adastra22 an hour ago | parent [-]

Just as general feedback, I think your experience matches up with more software developers than the GP. Haskell is really obtuse and unreadable for those who are not accustomed to it.

faangguyindia 6 hours ago | parent | prev [-]

i loved haskell but cross platform haskell deployment is still pain