Gleam: A statically typed language for the Erlang VM
gleam.run
gleam.run
Also, as an aside, I have to say that I'm a big fan of the proliferation of educational documentation written using the mdbook format. Since the compiler is written in Rust, I'd imagine that's where the author drew some inspiration from (much of Rust's documentation is written using that same format). It's easy to navigate, the code blocks render nicely, and I generally just find it pleasant to read.
Inko is a gradually typed, OOP language which uses erlang like isolated processes that don't share memory for concurrency.
I know, semantics before syntax, but if you can have both, why not?
Looks promising, love the ML-style pattern matching in particular, so clean and terse (wish Scala 3 would ditch the `case` keyword in match statements, but alas not happening)
1. Can multiline strings be indented, with the indentation stripped out?
2. Why do floats have their own operators?
3. What's the rationale for lists and tuples being different, and not just allowing lists to include different types?
4. How are module paths resolved?
5. Pattern matching looks great!
6. Function argument labeling looks great, I wish all languages did this!
7. Type system looks simple but capable, but in the User example it looks like the LoggedIn constructor isn't namespaced to User, so what happens if another type has the same constructor name? Conflict? On the same note, can you extend custom types after declaration, by adding constructors? (A little like Clojure protocols)
8. Can functions be overloaded, so you can provide multiple implementations for different argument signatures?
9. Can you implement custom operators, so you can for example `+` custom types together?
Looks like a really nice language and I'm looking forward to trying it, but like others have mentioned I too miss the concurrency aspects given it's built on Erlang. Big kudos for including interop details in the docs right off the bat though, this is a big plus in the feature basket!
Great work, definitely going to follow this!
Heterogeneous lists are a pretty advanced feature in the world of static typing. Since hetlists normally have to be fixed size anyways to do type-safe indexing, tuples are good enough for something like 99% of use cases.
It's like the difference between a graph and an adjacency list or matrix. A list being a linked list is an implementation detail.
At least to me. Mostly because linked lists are in general, awful ways to store data.
Not at present, though this would be a nice feature to have. Perhaps open an issue on the issue tracker if you have ideas for syntax or behaviour.
> 2. Why do floats have their own operators?
Gleam's functions and operators are monomorphic- they take precisely 1 datatype. There is no overloading, interfaces or type classes which would be used to have operators work on Ints and Floats but no other type.
We may support this kind of polymorphism later, though we want to ensure the design is right rather than rushing to any particular solution.
> 3. What's the rationale for lists and tuples being different, and not just allowing lists to include different types?
Type safe lists of arbitrary lengths that contain multiple types are difficult to work with, typically a single type is much easier to work with and covers the vast majority of use cases. If you wish to store multiple types in a list you would wrap them in a custom type (a tagged union) which would give the programmer an easy way to defferentiate between the different values in the list.
> 4. How are module paths resolved?
There is a global module namespace. The `src` and `test` directories are considered the root of the namespace, and the file path relative to there dictates the module path. Some more info can be found here: https://gleam.run/tour/modules.html
> 5. Pattern matching looks great!
Thank you! It's one of Erlang's best features.
> 6. Function argument labeling looks great, I wish all languages did this!
We got the idea from Swift! All the credit to them.
> 7. Type system looks simple but capable, but in the User example it looks like the LoggedIn constructor isn't namespaced to User, so what happens if another type has the same constructor name? Conflict?
Yes, you cannot define two constructors (or functions, or types) with the name module with the same name.
> On the same note, can you extend custom types after declaration, by adding constructors? (A little like Clojure protocols) > 8. Can functions be overloaded, so you can provide multiple implementations for different argument signatures? > 9. Can you implement custom operators, so you can for example `+` custom types together?
No, these features are not currently supported. We may add something in this direction at a later date if we have a good proposal that fits well with the languaage and provides clear benefits.
> I too miss the concurrency aspects given it's built on Erlang.
They are still there! They're just libraries rather than having dedicated syntax.
https://github.com/gleam-experiments/otp_agent https://github.com/gleam-experiments/otp_process
> Great work, definitely going to follow this!
Thank you
I feel like Haskell in general is yielding much cleaner code and once you start learning it, so much easier to follow the logic of a code and not get lost in all the noodles of commas, parentheses and unneeded brackets.
Originally Gleam had a more Haskell-like (or rather a more OCaml-like) syntax. Over time people generally expressed a preference for a more familiar C-like syntax, citing the unfamiliar syntax making it seem unapproachable.
Originally I thought these thoughts on syntax were unimportant (after all, syntax doesn't really matter) but after looking at the success of ReasonML (an alternative syntax for OCaml) and how it brought FP to a much wider audience, I decided that it would make sense to use this more C style syntax.
Gleam aims to be very accessible and welcoming to as many people as possible, so in this area we've gone for something I personally prefer less as it it may help others more.
I was always more of a Haskell user and had little C experience, so I found Rust a good fit for me. The type systems are quite similar :)
i.e. why would I learn and/or use this language?
Or is that too open-ended a question? I work with Elixir so I'm a big BEAM/OTP proponent, so I am curious to know about Gleam..
When refactoring in an ML language I like to think the compiler as being like a pair programming partner with excellent knowledge of the codebases. You indicate what change you wish to make, and the compiler shows you how to update the code to make that change quickly and safely with minimal testing.
I think ML's static analysis (potential error detection) and the BEAM's fault tolerance (implicit error handling) can compliment each other well, and I want to explore that space in Gleam.
I'm hoping to create a langauge that brings a new set of advantages to the BEAM ecosystem, to sit alongside Elixir and Erlang.
I've more detail in my conference talk from Code Mesh last year: https://www.youtube.com/watch?v=HaKR2kt-DXI
https://dotty.epfl.ch/docs/reference/other-new-features/inde...
As a Haskell developer I can appreciate this. We really take pride in our terse syntax, but it can be really jarring for people not familiar with an ML-style language. You are going to rob a lot of people the experience of using your implementation if you don't cater to their familiarness with other more common syntax. This is my first time hearing about your project, but I sincerely look forward to reading more about it!
I remember I was able to do all the Go's online tutorial in couple of hours because it was so familiar to me.
To your point, if you want to make BEAM more accessible to broader audience, making it more C like will increase the odds of success.
Thanks for the detailed reply! I'll keep an eye on it for sure. I never liked Elixir's syntax, so maybe Gleam will be the way to go if someone like me wants to play with Phoenix.
One day I would like to have a similar convention based framework for Gleam, but we have a long way to go before then.
pub fn any(tree: Tree(a), check: fn(a) -> Bool) -> Bool
I'd really prefer something like this (preferably with a syntax highlighter that would grey out the types): fn(Tree(a), fn(a) -> Bool) -> Bool
pub fn any(tree, check)
For some people, including myself, type annotations in Python are killing the spirit of the language. For me, there's no way to format those declarations to look reasonable, except for doing it like in Haskell or Elixir (@type).The syntax well not be changing again I'm afraid.
[1] https://github.com/appliedblockchain/tsql/ [2] https://github.com/appliedblockchain/parser-combinators [3] https://github.com/appliedblockchain/assert-combinators
It was my favourite talk of the conference outside the keynotes.
The first ten minutes is a good high level introduction to erlang itself.
I've been meaning to check gleam out since, this HN post has served as a good reminder to do so.
Let it fail, but not when you already know at compile time that it's wrong.
via https://news.ycombinator.com/item?id=19547418 (but no comments there)
Does it make concurrent applications easier to write? Does it make it easier to use the SIMD instruction set, how hard is it to get a function written in erlang to run on a gpu?
Is it like nodejs but more performant? I appreciate anyone who has answers here, I try to position all the relatively interesting things I don't have much time to dive in to in a mental model with opportunities for future promotion.
It's not a language oriented at maximum throughput of parallel operations.
It is a language that is oriented at providing very low latency (soft real-time) handling of lots of events.
When you write in Erlang, you decompose your program into lots (think upwards of millions) of small user-space processes, that communicate using asynchronous messages.
One side-effect of this architecture is that your application will scale in performance as you add more cores without having to change the code.
It's a great language for server applications, like NodeJS.
It also has a number of features that make it great for embedded and IoT type applications (which is why it was created in the first place).
Since one computer can fail, your programming language should be designed to be run on multiple computers. Erlang's the only language I know about that puts this philosophy first and foremost.
This proceeded Erlang by 24 years or so. I know it's not the language, and a combination of hardware and software, but they did change the instruction set of the HP 3000 to have paged virtual memory and "share-nothing" messaging system.
Also see the Pony language: https://www.ponylang.io/
It goes way back, 1998 was when they open sourced it, the language was in development and use inside Ericsson since 1986.
I have used Erlang's venerability in other Erlang postings, but for some reason I guess the open sourcing date stuck in my head.
But you might want to check out someone who knows better, like Rob Pike: https://www.youtube.com/watch?v=cN_DpYBzKso
Yes, Erlang makes concurrent programs easier to write.
Again it is better to listen to someone who knows more, like the creator of Erlang Joe Armstrong: https://www.youtube.com/watch?v=bo5WL5IQAd0 Any talk by him is great.
Erlang, in how i see it, makes it easier to write concurrent programs because:
1. Parts of a program communicate with other parts by passing messages
2. Every variable is static, as in if you declare that A is 12 you can not change it to be 13
Joe Armstrong had a way of explaining these things so that even i could understand, so i do recommend watching every talk you can find of him. I also recommend the interview he did of Alan Kay https://www.youtube.com/watch?v=fhOHn9TClXY .
But if you want to really get into concurrency i recommend a paper called "Communicating Sequential Processes" by C. A. R. Hoare. See http://www.usingcsp.com/
It's a "formal language" for dealing with concurrency. Go, and some other languages, implement a "CSP style" way of doing concurrency.
Erlang doesn't do SIMD, nor GPU. However, it does world scale low latency systems.
Of course, it's still up to the programmer to do everything correctly and Erlang is just a tool.
I don't quite like *JS, so i won't say anything about that.
"Concurrent applications" as in the C10M problem (http://c10m.robertgraham.com/p/manifesto.html): servers serving a lot of clients at once; usually with the added assumption of a diverse request workload (i.e. some requests are IO-bound, some are CPU-bound, some are long, some are short, etc.)
• WhatsApp used Erlang (on FreeBSD) to scale 50 engineers and ~1000 boxes to handling billions of chat-message transactions per second. (https://www.infoq.com/presentations/whatsapp-scalability/)
• A lot of modern financial infrastructure (banks, exchanges, etc.) is built on Erlang. If there's a financial system that can get away with not being hard-real-time, chances are someone's considered writing it in Erlang.
• Many massively-multiplayer games have Erlang in their backends or their networking middleware.
• And, of course, the original point of Erlang was to route (millions of concurrent) calls in Ericsson backbone telecom routers. It can be deduced from Ericsson's continued work on Erlang's "megaco" H.248 application, that they're using Erlang in their LTE IPMS gateway appliances, to route cell-subscribers' VoLTE audio- and video-call frames.
> Is it like nodejs but more performant?
Node.js is decidedly bad at this type of concurrency (although its libuv is used successfully in software like Nginx—it is rather Javascript's thread-isolation semantics that bottlenecks Node.js code, not V8 as a runtime.) A better comparison is with Golang. If you put Node and Golang on a spectrum of "ability to handle highly-concurrent heterogenous workloads", then Erlang is a point much further along that spectrum than either of them.
(Golang gets pretty far if the workload is homogenous, though. And you can get even further still on that spectrum with custom C or JVM code; but in the process, you'll likely sacrifice either 99th-percentile latency, or lock yourself into coding in terms of state machines. Erlang sits an an optimum where you can just write blocking code in actors and it does the right thing, predictably, even when you have millions of actors.)
Or something else?
Imagine you took all the AWS Lambda functions of all the different Lambda users, compiled them together into one binary, and then ran that single binary on one huge vertically-scaled server, allowing all the Lambda tenants to hit that server to run requests against their own workloads in the address-space of that single binary. That's a clear "heterogenous workload": one where you've spawned a bunch of green-threads within your process, and those green-threads are all trying to do different things. It's a million little job-shops under one roof (with, importantly, only one "door" in and out, that everyone has to coordinate to share); rather than one big factory where a million workers each have a defined job and there's a big loading bay that is declared from the top to be exclusively owned-and-operated by one department.
Erlang handles the "million job-shops under one roof" use-case pretty well. Golang kinda-sorta handles it, but kinda falls over, too, because the Golang runtime (and the whole concept of reifying typed channels as objects) isn't tuned for the case where each task has spawned an ephemeral digraph of communication paths between ephemeral actors. Channels have a Golang-runtime-system overhead just for existing, a bit like processes or sockets do for the OS. So, like processes or sockets, you're not supposed to just create a channel for each little thing until you've got a million laying about; you're supposed to reuse or share them (and the goroutines they address) between multiple workloads.
Idiomatic Golang code is written in a more dataflow-y pipeline-y SIMD-y way (the "factory with a million workers" above), where you've got static "actors" sending massive amounts of work between them, perhaps tagged with the request they're "about." When this is the 'data architecture' of your code, the Golang runtime can actually beat BEAM (using an equivalent architecyural abstraction, e.g. https://hexdocs.pm/flow/Flow.html) in both throughput and latency, since it's exactly what the Golang runtime has been tuned for.
The downside of the dataflow approach is that, compared to actors-as-little-Turing-machines, dataflow is a very hard data-architecture to reason about! (And also tends to mean that a logic error in any of your static dataflow worker-actors, causes a lot of "collateral damage" when it comes down, to the point that it often may as well bring down the whole system with it. A stall in an assembly line stops the whole factory; but a stall in a job-shop doesn't stop the whole economy!)
This is what I missed the most about Erlang when trying to use Akka's actors - the simplicity of writing a function that blocked until it received a message made for far more straight forward code.
Ok, WhatsApp does scale exceptionally well with very little resources, but those number aren't really accurate.
It is not billion messages / second, that would imply majority of the WhatsApp user were awake and send a message in the same second. But right now it may be more than a million per second. During its FreeBSD era ( They switched to Linux when Facebook bought them ) it was doing ~400K messages / sec during peak. And it is not 1000, during it FreeBSD era is was handling it with less than 150 server for Chat. And that was with a very non powerful 10-Core Ivy Bridge Servers. I often wonder if they could handle the same in 20 Servers when we have 128 Core Per Server today.
Node is mostly single-threaded still, right? The Erlang VM, Beam, runs truly concurrently. Also, a few large pieces of CPU work cannot block the scheduler indefinitely. I'm not aware of a runtime that works quite like it. Suits me well. I use it throigh Elixir.
It is not the fastest in any given metric from what I know but I think it delivers enormously well in real life scenarios.
For what it means for concurrency, it's not intended to be a high performant number crunching system, but rather one built to to make concurrency safe (which ends up being extremely performant for I/O bound workloads, and extremely predictable for CPU bound ones) - M:N threading model, immutable data, shared almost nothing, achieved via the actor model.
Not sure what compromises were made, but it was a sore point for users of the library for years to throw around untyped messages when the host language (Scala) provided such a powerful type system.
We do however have typed message passing libraries in Gleam that are in part inspired by Akka Typed.
the language was kind of underappreciated or considered very niche for a long time
With the rise of Elixir it’s been getting a fairly steady stream of mentions here.
I've made myself familiar with Erlang around 2011/2012? encouraged by the writing this amazing book [1]. which is still around and super amazing fast introduction IMHO. unfortunately I have never implemented anything of significance with it. maybe its time :)
This library is also worth looking at: https://github.com/gleam-experiments/otp_agent
Thanks for giving it a try! Rust compilation can be very slow, so we provide pre-compiled binaries for common platform. See the website and the releases page for more information:
Most chat happens in github issues and on IRC (#gleam-lang on freenode).
It's a great combo for more than that. I'm building a vm orchestrator on top of it and couldn't be happier (does everything from DHCP serving to virtualization library binding and resource matching). I do happen to have Phoenix in there, but it's for an entirely optional operator dashboard.
But also we're hiring, so if you're available and interested in seeing the product, drop a line!
If the author is reading is it possible to type message passing / processes? I couldn’t see this in the docs but i may have missed it
https://github.com/gleam-experiments/otp_agent https://github.com/gleam-experiments/otp_process
They could be improved but they are good enough for us to get started with.
let pid = spawn {
... hundreds of lines of code ...
let message = receive
message + 10
}
... lots of code here ...
send(pid, 20)
A compiler being able to infer the type of the message here will depend a bit on
how it processes code: does it go into the spawn first, or process that later?
If it does go into spawn first, what should it do when it reaches `message +
10`. And what if we were to process `send()` first, but it's actually sending
the wrong type? We'd probably infer the type to be an integer, even if the
receiving end actually wanted something else.Session types are one approach to dealing with this, but I have yet to see an implementation of this where it's actually pleasant to describe the session in the type system; most that I remember turn out horribly complex.
I spent a fair bit of time on this for Inko (https://inko-lang.org/) as it has a similar message sending system. Right now you'd have to use pattern matching to make things safe (this isn't released yet), but obviously I would like to check this at compile-time. Sadly I haven't been able to make this work yet, as there was always some corner case somewhere that meant that whatever I had implemented at that point wouldn't quite cut it.
For a while Inko had a special API for this that basically worked by sending types closures to the receiving end, that way type-safety could be enforced by making sure the right closures were used. Sadly this is not the most efficient, and in some cases still required type annotations.
One thing I did, but this may not work for Gleam, is to replace PIDs with just references to processes, and only allow obtaining such references by spawning a process. By not allowing one to just create a PID out of thin air, you reduce the likelihood of a process randomly receiving a message of a type it does not understand. A side benefit of this is not having to allocate PIDs, which I found out can actually be quite a bottleneck when spawning many processes (though Gleam being based on BEAM has no real choice in this matter).
> One thing I did, but this may not work for Gleam, is to replace PIDs with just references to processes, and only allow obtaining such references by spawning a process.
This is my current plan, though it does make interop with Erlang/OTP somewhat more tricky. I'm going to have to redesign some of the behaviours there to make passing around references to processes possible.
I have always wondered just how common it is in Erlang/Elixir to just fabricate a PID out of thin air and start sending messages to it. My gut feeling is that this is quite rare, but then again I'm not an Erlang user.
If you want to rubberduck about anything related to this, feel free to send me a message/Email/Tweet/etc :)
Information Security Requires Strongly-Typed Actors and Theories
https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3418003
Physical Indeterminacy in Digital Computation
https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3459566I am interested in having a feature like this but there is much work and research to be done first.
It would be largely a vanity project so I don't intend to spend time on it when we could be building more useful things.
I feel like this is Rust syntax on top of beam... but seems to be missing the point of Erlang.
As a keen Erlang programmer I assure you that Gleam will do its best to make full use of the runtime's fantastic properties :)
Saved.