Hacker News new | past | comments | ask | show | jobs | submit login
Things I like about Gleam's Syntax (erikarow.land)
151 points by lpil 10 months ago | hide | past | favorite | 56 comments



I really like todo keyword. Especially if it will be possible to make this warning into error in CI build only. So I will be able to run & debug unfinished work locally, but have guarantee that it won't get into master branch.


I don't get why it has to be a keyword. It can be a function with return type bottom.

In Rust it's a macro, but not for type checking. The reason is only that it can take an optional format string, and arguments, and functions can't have optional/vararg parameters (and also, format strings are statically compiled), a more dynamic language wouldn't have that problem.


The history on Rust’s todo!() is fun, because that’s actually not the reason why it was first made a macro.

First there was fn std::util::unreachable(). You had to import it, and the panic source printed out was …/rust/src/libstd/util.rs:132, which wasn’t very useful.

I wasn’t happy with this, and so proposed turning into a macro <https://github.com/rust-lang/rust/issues/8991>, which fixed both of those problems. The import thing alone could have been resolved by adding it to the prelude, but the panic message file/line thing couldn’t be fixed at the time other than by turning it into a macro.

At some point (still before 1.0.0) unimplemented!() was added as “unreachable!() but with a different message”.

In 1.20.0, these macros got the ability to take format arguments, because why not, since they’re already macros anyway, and adding an extra message is sometimes quite helpful. <https://github.com/rust-lang/rust/pull/42155>

Until this change, the sole reason for them being macros was the file and line number thing.

Then from October 2019 (though not stabilised until 1.46.0, but the standard library could immediately use it) came the really amusing part: this original reason became obsolete with #[track_caller].

So really there have been three distinct answers to “why is it a macro?” for three phases of Rust’s life:

• Before 1.20.0: “so the panic output gets the right file and line number”.

• Between 1.20.0 and 1.forty-something: “so the panic output gets the right file and line number, and because it takes format args”.

• Since then: “because we didn’t have #[track_caller] before 1.0.0 (since changing it from a macro to a function now would be a breaking change), and more recently because it accepts format args”.

If we’d had #[track_caller] back in 2013 (or any plans in that direction), we wouldn’t have made unreachable a macro, and then unimplemented would have also been made a function, and then format args stuff couldn’t have been added without introducing new macros, and I’m dubious it’d have happened.

Little things in history and how they affect the future can be fascinating to observe. This is a tiny and pretty harmless point, which I largely remember because it was one of my early contributions (and I haven’t made a large number in total, to drown it in volume), but there are many other things in Rust where little pragmatic decisions long ago have been causing all sorts of pain in trying to properly generalise them now. Const generics is far from the worst, but perhaps the simplest to observe, especially in how things have largely eventually been fixed (whereas various others have just ended up dead in the water). The [T; 0], [T; 1], [T; 2], …, [T; 12] implementations and similar have caused quite a lot of trouble.

(todo!() was added in 1.40.0 as a third member of this family of “panic!() but with a different message and intended semantics” macros.)


Gleam has no subtyping so it has no bottom type, but you could still make a function that returns an unbound type variable.

However there's no advantage of implementing this in user-land. Having it be part of the language means the compiler can do things like emit a warning when it is used, ban it in release builds, and so on.



This isn't totally unusual. In language like Haskell you can put something with a maximally general type (a "bottom", e.g. `undefined`) in as a placeholder anywhere in the program and your compile will go through. If you want to get even fancier you can put a "typed hole" that says something like "I don't know exactly what this is yet, but it's definitely going to return a list of ints".


Ocaml has "failwith", basically the same as todo. You can pass an message too.



Rust has todo!() and clippy has a lint you can configure.


Gleam looks great.

The only thing about Elixir I'm unhappy with is the typing. Gleam seems to bring good things from the Rust type system to the Beam (especially sum types, pattern matching and avoiding nil).

My question though, I've been really happy with Phoenix for web development. How well would it work to develop a Phoenix web app with Gleam? Would it even work with LiveView?

Could I use Gleam on the frontend too (it also compiles to JavaScript)? How would that work with LiveView?


FYI: Elixir may be getting static typing in a future version.

https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...


Does Elixir's Set-theoretic Types support declaring restricted subset of types for modelling purposes as often done by ML programmers? The "Type-Oriented Programming" or "Domain-driven Design"?


Gleam looks like a new language built to compile down to the BEAM. While you could call elixir or erlang libraries from Gleam code, using Phoenix probably wouldn't be useful. I imagine a new framework would be written for Gleam.


The elixir macros dont work well from gleam, you can however 'call' it, but you need to wrap all the macro code in elixir functions then call them from there.


I'm a bit late, but I just found out that there's a version 3 library for Gleam called Lustre, which is an Elm-like framework for frontend. And people seem to be able to make it work with Phoenix (compile to javascript in /assets/ folder directly), LiveView should also work but haven't seen anyone trying it yet.

There's also a React wrapper


I’ve been learning Gleam the past couple weeks and it’s got amazingly well-developed tooling and community for its age. Overall solid documentation, a full Exercism track with active mentors, good compiler hints (and LSP support) and an active Discord channel.


I can't stress enough how decent and high quality the discord users are. Big respect.


The discord community is fantastic!


> Succinct alternative clause patterns using |

This is brilliant. I wish Haskell had this too.

> Only case statement, no if

I don't quite understand the arguments here. How is that verbose "case" example any better than:

  if if_boolean
    then "case statements only"
    else "you didn't get a choice"

?


Because Erlang's "if" doesn't work like that. It looks like this:

    if
        IfBoolean -> "case statements only";
        true -> "you didn't get a choice"   % yes, it's "true", not "else"
    end
In fact, Erlang's "if" is actually an if-elif chain that takes guards (unlike "case" which takes patterns) as test conditions: those are restricted set of expressions that basically allow only constants, variables, arithmetic/boolean operators and comparisons and very few built-in functions (mostly of the is_type/1 sort), and that's it. Guards are evaluated one by one until one of them evaluates to true; if none do, a run-time error is raised.

It's a very unwieldy construct and at my org, we mostly try to avoid it because "case" is clearer in like 98% of the cases.


I think the argument is that it's exactly the same, so why learn both? I've definitely lost seconds (maybe even minutes) to refactoring if statements into case statements and vice versa in Haskell.


Why have two things that do exactly the same?


Because it's more readable and easier to write


That's your opinion, but I also get messages from people saying the opposite.

My hunch is that familiarity is actually what makes something more or less readable.


Gleam looks really interesting. I'm surprised, for some reason I was under the impression it was more like Sorbet or Mypy with type annotations in existing code and I had no desire to check it out at all. I'm now intrigued and will probably pick it up soon!


What's the point about using a functional language like Gleam / Elixir for web-server applications?

I used the work for WhatsApp in Erlang, and there's a strong use case for OTP / BEAM for messaging apps, but it feels useful for anything else - especially a webserver where frameworks like Rails and Django are so easy / well supported. What do you think I'm missing?


There is a strong case to be made that BEAM/OTP is a more natural fit for handling thousands or millions of web server requests. Django and Rails are definitely powerful frameworks, but there have been repeated and justified criticisms of their performance. These frameworks are built upon scripting languages designed for convenience rather than performance.

OTP application failure handling and recovery out of the box is also fantastic.

Beyond performance though, I think functional languages really fit well into the common unidirectional data patterns used when building out a web app. Typically you're running a request through a pipeline of functions like parsers and other middleware, shaping a response to send back to the user before destroying all used computing resources as quickly as possible. Modern languages like Elixir and Gleam provide some great capabilities (pipes, pattern matching, comprehensive stream-based map/reduce capabilities, etc...) for building these pipeline-style workflows in a very natural way, because it's really a quite functional approach already. There is also no real need for building true OOP-style objects when you're just going to throw everything away in a few milliseconds.


I am way more productive with Elixir than any other stack i have tried. It’s actually insane. Things that seem almost unimaginable are so easy. Building an application that gets way larger than just a web app is so much more doable too. You’ve got everything you need right there and it’s so easy to organize in a conventional way. Testing every inch of it is so much easier.

It’s a brilliant technology. There’s a reason why there are so many evangelists.

So many people experience a new found joy of programming when they use Elixir.

And there are so many times I think “this would be so much easier” with Elixir when I work with other langs.


> especially a webserver where frameworks like Rails and Django are so easy / well supported.

[boilerplate response about OTP, pattern matching, LiveView]

It's honestly mostly aesthetics, there isn't anything particularly rational about preferring it over Rails/Django. Like it just feels better. You can mostly do what you do in Elixir in other languages, but it's comfortable in my little Elixir project folders.

I have nothing against Django or Rails, but I noticed that as soon as I asked the question of "how do I do a task in the background" "how do I keep longrunning state without over-relying on the DB" it got way more complex. Setting up carrot or whatever Django scheduling library was painful. In the BEAM family of languages, as you know, you just spawn a process to do background tasks or keep state. It can always be revisited with something more complex when the time arises.

Starting an Elixir application also just feels more robust. It has my back. It's not going to just stop working without putting up some kind of fight. I like that I can do things like start the other application components without starting the webserver. Elixir makes me feel like my code is "alive", like it's an organism with living components. Again, not rational.

Oh shit, I made fun of the boilerplate OTP statement, but I do really like the way it prescribes application structure. Being able to think of the application as a tree of components is really helpful for my mental model of what's going on.

Ecto is also a big deal for me. Nothing beats the LINQ-style query building, then dropping into plain SQL as needed. If I ended up switching languages, it would need to have something like LINQ in it. So maybe C# lol.

Binary pattern matching is always something I miss when I'm not using Elixir. Especially for parsing or converting data.

But you're really not missing anything if Erlang didn't seem remarkable when you were using it.


One thing that I do miss is the pipe operator, I'm doing LFE now which I can use the arrow operator which seems to get me most of the way there.

There is no "ecto" equivalent for LFE yet, that'd be very nice, but probably above my skillset.


Short answer: scheduler.

Long answer: web applications have usually tons of independent request. Each request does its own thing, might never communicate with other requests, touches the DB and returns. You have two choices in such scenarios. You can either maximise throughput or minimise latency. BEAM's scheduler choses the latter which is usually what you want for web. More detailed discussion: https://tkowal.wordpress.com/2015/01/27/the-unintuitive-late...

With:

- per process garbage collection

- one BEAM process per request

- short lived HTTP processes

you might never trigger garbage collection, the whole memory is deallocated at the end of requests.

It basically means, BEAM gives you high level languages like Gleam or Elixir that are fast to write and prototype as Rails or Django, but are almost as performant as handwritten memory managed applications written in low level language.

This is magic sauce in startups: the freedom to go fast, prototype and iterate with scalibility issues popping up waaay later than in Ruby/Python stacks (sometimes never - if your startup is successful without supporting whatsapp's user base :P)

And that's just if you are donig plain old HTTP backend. With new reactive architectures, keeping persistent processes server side and using LiveView instead of JS frameworks can again cut development time.

And then, there is support for AI with LiveBook and NX... It is weird how many hard issues BEAM just makes much easier.


Functional languages map really well for web-servers as all you're doing is receiving a request, transforming it and sending it back, and that's really what functional programming is all about.

So I ask you, what's the point of using imperative or object oriented languages for web-server applications?


Since you mention Rails, have you seen https://www.phoenixframework.org/

Question: what’s the status of WA open sourcing their typed Erlang project?


I remember reading somewhere,WA had to go back to do some improvements, so there was going to be a delay. Was it this one https://github.com/WhatsApp/erlt ?


You mean the type checker eqWAlizer? That is out there. Not sure if they got further on typed Erlang.

Ar Code BEAM in Berlin just now there was a presentation on a type system effort much like the Elixir one.


I am reading that right, a type-system like elixir's new type proposal for erlang ? Can you provide more details, Thanks in advance.


This talk: https://codebeameurope.com/talks/etylizer-set-theoretic-type...

No clue if it will get into Erlang since I don't think it started from inside Erlang/OTP. But from conversations the team seem open to an effort like this.


Just to be clear, this is a 'typed' functional language something that erlang is not.

Gleams type system has reduced the crashes in my code, however I do still supervise them and distribute tasks across systems (See https://github.com/wmealing/gleam-otp-design-principals/blob... )

Non beam languages not get this kind of capability out of the box.


> but it feels useful

Did you mean "It doesn't feel useful" ?


I find it strange that you, having worked with Erlang, struggle to see a use-case for BEAM languages in the context of web development.


Looking at the description, what I can't understand is why they adopted the weird (param_name label) construct for named parameters. IIRC the same is Objective-C and Swift.


Oh I wish more languages adopted this. It is nice to be able to name the externally visible parameters such that the invocation is natural to read (eg. DB.select(~from: userTable)), but it is better to have more explicit names on the implementation side (eg. tableName)


Honestly, I don't see the reason, or the need, and I've used a dozen or more languages at this point.


My guess is that normally parameter names are not part of the API, so they added an explicit API name.

I think it's a bit silly though. I would have gone for explicitly making keyword parameters.


Internal/external names distinction. It's always ugly.


Yeah, I can't understand the need for it :)


It allows you to specify reserved words as parameter names which you would otherwise be unable to use.

Ruby has this problem with keyword parameters and you need to use `binding.local_variable_get(:NAME)` to get such a reserved word.


There are not that many reserved words, and you can always use a different word ;)


Sometimes a different word is the wrong word.


First exposure to gleam here and I'm impressed. I am somewhat puzzled though, as I don't see any references to otp/actor examples on their "language tour", which I would have guessed is the main use case coming from Erlang for at least the server side. Are they deemphasizing otp since it targets both Erlang AND JavaScript? Presumably for the gains of "one lang" across the stack like node/deno provides?


OTP isn't part of Gleam the language, it is a library. This is true of Erlang too, but in Erlang they tend to sell both the language and the actor framework at the same time.

With the introduction we want to stress the language rather than that actor framework you may or may not chose to use in your programs. 95% of programs don't involve an actor system, and learning to use OTP is very challenging, so this shouldn't be the first thing you're greeted with when learning the language. That's the theory at least!


Pardon my ignorance, but my thought was that Erlang/OTP was pretty much the reason folks used Erlang in the first place. I guess that's no longer true these days?


All programs using the BEAM runtime will be using OTP, but that doesn't mean you'll be actually writing OTP framework code, you'll almost always be using higher level interfaces instead.


Looks like it is an external library[^1]. Readme states it is experimental and lists some limitations.

[^1]: https://github.com/gleam-lang/otp


This is also true of Erlang. OTP is a series of libraries and not part of the language.


No thank you.

  1> (lflow '(1 2 3) (append '(4 5 6)))
  (1 2 3 4 5 6)
  2> (lflow '(1 2 3) (append '(4 5 6)) reverse)
  (6 5 4 3 2 1)
  3> (lflow '(1 2 3) (append '(4 5 6)) reverse 2)
  4
  4> (let ((prepend (op append "other ")))
       [mapcar prepend '#"hello world"])
  ("other hello" "other world")
  5> (match-case '(1 (2 3))
       ((@x @(as inner-list (@y @z))) (list x y z inner-list)))
  (1 2 3 (2 3))
No "import this" or list.that; just language.




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

Search: