Things I like about Gleam's Syntax
erikarow.land
erikarow.land
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.
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.)
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.
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?
https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
There's also a React wrapper
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"
? 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.
My hunch is that familiarity is actually what makes something more or less readable.
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?
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.
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.
[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.
There is no "ecto" equivalent for LFE yet, that'd be very nice, but probably above my skillset.
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.
So I ask you, what's the point of using imperative or object oriented languages for web-server applications?
Question: what’s the status of WA open sourcing their typed Erlang project?
Ar Code BEAM in Berlin just now there was a presentation on a type system effort much like the Elixir one.
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.
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.
Did you mean "It doesn't feel useful" ?
I think it's a bit silly though. I would have gone for explicitly making keyword parameters.
Ruby has this problem with keyword parameters and you need to use `binding.local_variable_get(:NAME)` to get such a reserved word.
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!
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.