As someone familiar with Rust who has been interested in Erlang/Elixir, but hasn't taken the plunge, this could offer an really nice compromise for helping to learn without necessarily needing to start from scratch. It definitely seems worth keeping an eye on!
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.
See also: https://github.com/purerl/purerl, an Erlang backend for PureScript, which is a lot more mature but unfortunately doesn't get the attention it deserves.
Wow! So far this looks like the language I wanted to make! I wanted something like Elixir but with type declarations being part of the language as opposed to being annotations. I’ll be looking at this all weekend! It’s got the right ergonomics.
Not an Erlang user, but I must say the syntax is beautiful.
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)
Looks interesting and quite approachable for someone familiar with C-style syntax. I was looking through the language tour and it's nice and succinct. A few things that came to mind:
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!
>What's the rationale for lists and tuples being different, and not just allowing lists to include different types?
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 depends on what you want to do with them. They have constant time prepending and support structural sharing, both highly desirable features in many use cases.
> 1. Can multiline strings be indented, with the indentation stripped out?
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.
Looks interesting.
I only wish it used Haskell's type annotation. I've been learning Haskell recently and I'm really liking how easy it is to read and digest codes.
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.
Martin Odersky followed the same logic with Scala and he has since expressed some regret, especially as more and more people become familiar with Python, which is more succinct:
I remember that post, interesting stuff! :)
I'm hoping to be more concise than Scala and Java, and to have a much simpler grammar. It is a tricky middleground to reach
> Gleam aims to be very accessible and welcoming to as many people as possible
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!
It's why I am learning Zig instead of Rust really. I think it is more like C, and is a lot less to bite off. Probably a wise choice for Gleam, but I do like Haskell/Idris syntax too wrt to types.
Existing BEAM languages such as Elixir and Erlang do an incredible job when it comes to scaling and fault tolerance, however I believe that other languages do a better job at providing the programmer with a fast feedback loop for rapid development and refactoring.
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.
fair point! I guess we can also look at the success of Golang and attribute it to similarity to C.
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.
Well, you can't make everyone happy. Personally, I've started with the C family of languages (C, C++, C#, bit of Java), but after going through Python, Ruby and Elixir I never want to go back to curly braces. The other thing that puts me off is mixing types into the declarations - I treat types as comments and only reach for them if I need to dig deeper; in most of the cases the names of the arguments are self explanatory and types make it really hard for me to parse the function declaration. So instead of this:
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).
It's true, I like the feel of Haskell type annotations as well, but I'm working with js/flow in day-to-day job; finding mysqlf moving towards those one-line-type-annoations to mimick Haskell, I'm surprised flow actually handles most of the code, even not so trivial ones; inference is good enough that those one liners are most of the time all that's needed, examples are here [1] [2] [3] if somebody wants to peek.
The guy [1] who runs this project is super nice, about a year ago I was working on my own Rust based VM and he dropped in with a couple of helpful tips and ideas.
I can absolutely confirm this. A wonderful combination of patient, kind, and super-knowledgeable. He was a very good sounding board in the early days of one of my projects.
Amazing. Adding the ability to do type driven design on top of the fault tolerance features of Erlang will make it easier to create correct AND fault tolerant applications.
Let it fail, but not when you already know at compile time that it's wrong.
Forgive me, I'm ignorant -- what's erlang for. "Concurrent applications" is the raison d'etre I see espoused. What's that mean?
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.
Probably a better description is that Erlang's raison d'etre is -fault tolerant- applications. Concurrency and distribution are just natural results of prioritizing that (because to isolate faults you need strong concurrency primitives, lest some number of bad behaving processes prevent others from being able to succeed, and you also need distribution, lest hardware faults lead to system failure). But, yeah, there are lots of good links attached.
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.
"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.)
• 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.)
> Erlang sits an an optimum where you can just write blocking code in actors and it does the right thing
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.
Can you say more about Go and homogeneous workloads? You might be referring to mixed IO- & CPU-bound tasks, and Go's former inability to preemptively schedule tightly written loops? (Solved along the way: https://github.com/golang/go/issues/10958)
Nah, just talking about the fact that Go doesn't scale well to using millions of channels to talk to millions of goroutines, each one executing as part of a small goroutine-cluster running independent functionality with its own requirements for data-architecture.
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!)
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).
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.
Afaik, Erlang is all about that actor model, message passing and abstracting the lower levels away, so that ideally does not even notice that an actor is running on another machine. All of that of course with automatic restarting of failed actors. Basically it is Kubernetes done right and probably has way more functionality, as it predates all the Kubernetes stuff by a long shot and development hasn't stopped.
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.
Erlang is the functional language, but the OTP (Open Telecom Platform)is the collection of Erlang libraries or middleware that give it its concurrency prowess. It runs on the BEAM, a VM for Erlang. It goes back to 1998 or thereabouts, and was mainly implemented Ericcson by Joe Armstrong and Robert Virding and some others to handle telecom switches hence the Telecom in OTP. Joe passed away just last 20 April 2019.
Since no one has mentioned it so far, I'll go ahead and link a very insightful paper that the creator of Erlang actually wrote about how fault tolerance from the actor model make concurrent programming a breeze:
It is well suited to running concurrently as in, many cores, and in a distributed fashion, across many computers. It was built to run telephone switches and similar highly available, high performance gear in a way that is resilient to failures.
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.
A little disappoint that it doesn't have message passing, one of the most powerful weapons of the Erlang family. But the language looks clean and neat. I can understand it within couple of minutes
People have been trying for, IIRC, 15+ years to add a full-fledged type system to Erlang, and the distributed nature keeps defeating them. This problem may never be solved.
See Akka Typed (based on the Actor model) for an example of typed message passing in a distributed system.
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.
I came to Erlang (and HN) after this time period, but apparently there was a period during which the language appeared on the HN front page a ridiculous amount, sometime around 2010 perhaps?
With the rise of Elixir it’s been getting a fairly steady stream of mentions here.
I'm starting to see the recent interest as well, which I just wanted to complement.
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 :)
I'm interested in Erlang, since I first heard about it when I wanted to write plugins or work on the Wings3D program back in 2003 or 2004. I chose playing with LFE over Elixir, because I prefer Lisp syntax to Ruby-like syntax, but limitations of the BEAM, and core have some gotchas for running a Lisp. I'd like to try Gleam. It looks clean, and I like the fact that it is typed. The Erlang/OTP and other BEAM languages work well for what they are intended for, but if you do any number crunching they are slow. The Elixir/Phoenix stack seems to be a great combo for web developers. I'll give it a try by compiling it on my Windows box with Rust, since my iMac Pro 27 is mid-2011, and my Linux notebook is busy right now for other jobs.
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:
Thanks! I only saw Mac and Linux on the installation page. Nice to see the windows binary on the releases page. I have all three systems, but my windows box is my main go to, and my newest and most powerful laptop. I'll try it after work tonight, and get back to you with any questions. Is there a forum, or just use Github?
> The Elixir/Phoenix stack seems to be a great combo for web developers.
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.
Is Phoenix only for the optional operator dashboard?
I ask because I have not tried Phoenix. I've played with Elixir since it came out, but only Elixir.
yep, although I am working on rewriting the user frontend in Phoenix, that is a political hurdle to get out into the wild. Most people learn Phoenix, then elixir, but I also learned Elixir, then Phoenix, and I think it gives me a stronger handle on how the VM works under the hood.
It's not, sorry. There's a lot of security-related things that are still hard-coded that will take some effort to decouple, but I am working towards that. But I've open sourced a few low-level bits of it, like dhcp, tftp, a few still-experimental inter-controller transport protocols, and they're available on hex (but not virtualization yet, but probably by the end of the year). There's a few things like "building vm images", "provisioning host images" that might come in soon.
But also we're hiring, so if you're available and interested in seeing the product, drop a line!
It's not supported in the core language but we have some type safe bindings to messages and message passing as libraries. We may upgrade these to being part of the language itself but I don't want to rush this and get stuck with a sub-optimal design.
Type safe process communication is a tough nut to crack. You can sort of handle
it by doing away with PIDs or by at least making them generic, but that leaves
out corner cases such as sending a message to the current process. Type
inference of the message types is also hard, imagine something like this (using
pseudo code here):
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).
Thanks for sharing your thoughts! It's quite reassuring to read your post as it sounds like we have had many of the same ideas about types and processes.
> 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.
> This is my current plan, though it does make interop with Erlang/OTP somewhat more tricky.
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 :)
Creating the PID data structure is rare however it's common to give names to processes and then throughout the rest of the program make the assumption that there a process behind the given name that will handle the messages you are sending. I think we'll have to avoid this practice altogether.
For those interested in the underlying theory of Actors see the following:
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=3459566
Dumb question from a non Erlang person - can this be used along with Elixir? I’m looking at Phoenix LiveView but would love to use Gleam for core functionality. Is this anything like Scala/Clojure and compiles down to Erlang, I’m guessing that is possible?
Yes, it can be used with Elixir. We don't yet have any integration with mix (the Elixir build tool) so if I were to use both in the same project I would probably create an umbrella style application with one Elixir application and one Gleam application within it.
This is really neat! Have you considered an ML-like module system, with functors and such? I'd be curious if they could be connected with Erlang's processes somehow :)
Is it just me or does the introduction not mention equivalents to Erlang's messaging operators, or how to use OTP at all? Or _anything_ about proceses at all!?
I feel like this is Rust syntax on top of beam... but seems to be missing the point of Erlang.
The website current is an introduction to just the language itself. Later we have detailed documentation on the subject of OTP, but there is a lot of work to do first. Gleam is a very young language.
As a keen Erlang programmer I assure you that Gleam will do its best to make full use of the runtime's fantastic properties :)
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.