Gleam, Coming from Erlang
olano.dev
olano.dev
> The single remarkable feature of my reader is that I don’t sort posts chronologically but by “reverse frequency”. If I follow a news site that publishes twice an hour and an essayist that publishes every two months, I want the essayist at the top of my feed if they published yesterday, even if there are 30 more recent news posts.
I love the idea of seeing the less frequent posters at the top!
I think the author hit on a core challenge gleam will have to face, outside of dx. its main niche would be highly scalable type safe server apps, pretty similar to what scala is/was originally imagined as.
Its very hard to occupy that niche when by definition the projects you look to take on are large enough for languages to be heavily scrutinized. Your CTO wont really care if you choose rust, node, or go for some internal tooling. They will absolutely care about what is being used for core distributed services, and that just makes adoption hard.
With how many people in the React/Typescript ecosystem are super excited about full stack type safety, this feature of Gleam feels like it's flying under the radar.
The pieces are there to have an unparraled DX for building everyday web apps, but the pieces haven't been assembled in a way that "clicks" for most people. At least, not that I've seen.
The effort to make JS work on the server has been much more successful than any attempt to make server languages work on the client. Nothing jumps out to me as an obvious technical reason this is the case -- maybe it's moreso an imbalance in effort & attention?
https://www.compositional-it.com/news-blog/fsharp-react-seri...
It's easy to make JS work on the server like it's easy to make anything viable on the server: it's just one machine running your code, and the runtime only has to interoperate with a well known interface (the OS) that barely changes. And then consider that a web server's interface to the "user" is over a single, abstract port.
In a browser application, you have to interface with JS to drive the UI building blocks (DOM and company). And the web API is async. And you have to build an interactive UI. And abstractions on top of all this are extremely leaky because they are hard to simply paper over and pretend they don't exist like you can on the server.
Often when I see a JS replacement on the browser, it seems to offer very little over JS beyond "hey look, it's not JS" which just isn't enough to justify the penalty.
Finally, the hard part of UI development is stuff like state management and deciding when things should rerender. Switching to a different language alone doesn't give you that. Figuring out a solution in JS, like MobX + React for example, probably does more for you than trying to make a whole new language work.
JS being the monolithic tyrant that other tyrants see with envy doesn't help.
In the server and elsewhere you have several major langs with big enough sub-cultures and a stablished ethos of sharing, even if that mean the second biggest tyrant of C.
JS developers overshadow any other tribe, so that mean that population could have much less exposure to polyglot alternatives.
And then all that libraries, that are made to make sad any FFI you can trow at it.
There are probably tens of thousands of developers waiting in line to switch out JS. The reason they can’t is not even that it can’t run in the browser (many can) but that it interacts with the DOM natively, which makes it the only language that can be used in the browser without masochism. In turn, the DOM is the only UI that is truly cross platform, which makes it the by far best and cheapest option because everything else is either hobby or proprietary in 10 different flavors depending on which fiefdom you build your product in. Basically, JS isn’t popular because of ability, but its interoperability.
The effort & money that has been poured into tech after JS was created would dwarf anything beforehand. Yet, we live today by the open standards that were developed before Bezos etc started purchasing private islands. So I’m a little hesitant to blaming JS for being backwards when the rest of the world couldn’t come up with an alternative despite having 1000x (not an exaggeration I think) more time and money to do so.
What I didn’t know about myself is that I hate JavaScript a little bit more than I hate Java and for different reasons. So I was volunteering for stories in one when I was feeling frustrated with the other. Now it’s just more JavaScript unless I work on CLI, CI/CD pipelines, or telemetry bugs, which are still almost half JavaScript. I cannot get away from it enough and every time I do that takes someone else’s opportunity to do the same.
I worry that all Gleam all the time would arrive at the same conclusion.
Javascript is out of its sweet spot quite a bit, which is how we end up with typescript.
However statically strongly typed code is out of its sweet spot where prototyping and to an extent refactoring are concerned. And mercifully we now have progressively typed languages trying to fill that space.
1. Full access to the BEAM.
2. Full access to OTP behaviours and other features.
3. Lisp-style macros.
4. Late-bound function calls.
5. Dynamic message passing.
6. Native supervision trees.
7. Internationalization.
for static types. Doing so feels like trading my car's engine for a suction cup phone holder.
(EDIT: Fixed formatting.)
Gleam is not entirely foreign but it sets itself further apart; it's all in on cloning Rust.
(Elixir's set-theoretic type system is _not_ a constructor-soup ML deal, by the way. The people who talk about "static type systems" now are people who caught the ML bug and think "is like ML" equals "types", and they will not find it satisfying, just like they don't find existing Dialyzer types satisfying. So that's the niche for Gleam.)
[1] https://blog.nestful.app/p/gleams-lustre-is-frontend-develop...
This was the biggest thing I noticed as well- core OTP concepts like gen_servers and supervision trees aren't covered in the language tour, the docs, or even the "Gleam for Erlang Users" page... it makes sense that it's missing if it's pre-1.0, but I was very confused when I didn't find anything about it at all.
Edit: My (forgotten) comment when this was posted 10 months ago [2]: "So pleased to see this. Had fun playing with the language but at the time learning OTP seemed a big ask"
https://www.totaltypescript.com/typescript-5-2-new-keyword-u...
Not sure I'm a fan of it tbh
Not to say say that Gleam doesn't had has it's place, it is a very interesting language, just that Elixir is already a good and battle-proven language.
Looking at you C++.
OCaml?
I struggled with this as well when I first started programming Rust. Until I got some great advice on the rust-users forum one day: just clone.
You don't have to struggle with the borrow checker, lifetimes, or anything else if you don't want to. Just clone everything. It's usually all your Pythons, Gos, and anything else with a GC is doing anyway. Clippy will tell you the easy places you can fix it. And when you get better and more confident, then you'll spot it yourself too.
So just clone, who cares. The RAII is going to clean up after you no matter what.
GC langs don't do clones (copying), that's the point of the GC.
Granted, I'm a systems programmer, but I find the difference between Rust's memory model and a managed memory model (like C#'s) not very big. The only difference is that it's trivial to leak resources in C#, and it's very hard to leak resources in Rust.
Happy to get recommendations of a language that has both a GC or similar managed memory model, and also can manage all other resources the same way.
What Rust does right is what C++ tried to do; RAII. Memory is a resource, file- and socket-streams are resources, mutexes are resources, and they are all handled via the same principle that we all know and intuitively understand: Scopes. By extension, reference counting enables doing an AND of all the scopes.
I have never found that to be more mental overhead than, say, python, C#, or Golang, where forgetting to close a file, or forgetting to call Dispose(), or doing it twice by accident, or using mutexes, is a common source of nasty bugs. Extend this to the concurrency model Rust has, where both threads and async can be used, and all the resource management and borrowing just cleanly extends to that to avoid concurrency bugs, and you have unparalleled resource management.
Leaking resources in Rust is pretty trivial: create an Rc cycle with internal mutability. By contrast, in languages with a GC, cycles will be properly collected.
And there is nothing preventing GC languages from tying their non-memory resources to the lifetime of a value like Rust. See Python files, which are closed when the file handle is collected (ie. when all variables holding it go out of scope), and if that's not explicit enough, there's the "with" keyword to explicitly introduce a scope at the end of which the file is closed.
It's trivial to do on purpose, but difficult to do accidentally.
In Python, you need to handle resources differently and introduce a language feature to not leak them.
That's the difference you're pointing out.
That's a CPython implementation detail.
https://docs.python.org/3/reference/datamodel.html
> Do not depend on immediate finalization of objects when they become unreachable (so you should always close files explicitly).
foreach (var line in File.ReadLines("example.txt")) { ... }
Here, file handle will be opened once you start enumeration and will be closed through enumerator disposal which is one of the calls foreach is desugared into (to be accurate, this specific implementation will also close the handle when reading the last line, but I'd expect user-authored type to do handle closure through dispose implementation only)I agree that Rust implicitly inserting drop() by proving the exact place the value gets dropped at is superior, but C# experience is not exactly terrible either.
People keep forgetting CLR was designed to accommodate C++ as well, and during the years some of those capabilities have been exposed to other languages in more easier ways than reaching out to C++/CLI.
The same applies to C#: You program your resource freeing with Dispose (okay, fair so far), and then if the caller forgets to Dispose, you leak. Again, the default behavior is that resource cleanup is a part of the explicitly implemented logic, always.
Compare that to Rust, where you can explicitly drop(), but once a value goes out of scope, its resources are freed.
This is about default and consistent behavior. C# and Go handle memory as a special resource you only sometimes, only in some ways, need to worry about (new, make) but most other resources (files, sockets, threads (if they exist)) become an afterthought and have special logic the programmer needs to worry about *by default*.
and exhaustive pattern matching + result types should help as well pushing issues out of runtime.
Solely or alone (only) computation efficiency, otherwise use NIFs and/or Ports.
I don’t understand why they didn’t make promises a first class citizen and added something like `await`. We’ve learned this lesson in JS already.
As far as I can see, `use` is a superset of the `await` transformation that just happens to work in a lot more situations. It circumvents callback hell when you're working on the Javascript target [1] but also de-indents lots of other code like handling results [2].
[1] https://github.com/gleam-lang/javascript [2] https://erikarow.land/notes/using-use-gleam
The author of the article has an intuition that "use" is helpful for error handling, and it definitely is because one of the core benefits of monads is chaining certain categories with a guarantee that nothing will blow up in the process; however, use cases are broader, as you can chain all sorts of things.
Looking at it now, it's actually akin to clojure's threading operator, which basically just simplifies syntax when making successive function calls (argument threading). Whatever the case, it's not just for errors/async, but more so for chaining without causing a bunch of unnecessary indents to occur.
> It seems to be a direct riff on Haskell's "do notation" syntax sugar
It's not this at all! It may seem this way at first as Gleam's `use` is used in many of the same places as Haskell's `do`, but `use` is a much more general syntax which is used for a much wider array of things. They also work very differently, `do` being constrained by types and laws, while `use` being purely a syntactic feature with no type restrictions or laws.
Languages that have explicit goals of simplicity, like Go, have become increasingly attractive to me after working professionally with Scala for the past 3 years. Though I love Scala's expressiveness, the fact that there are a million ways to accomplish each objective makes decision paralysis and bike-shedding in code review inevitable.