I found my new programming language to focus on: Gleam1. I like to deep dive and learn new tools in depth, always looking for general purpose tools and ones scalable enough to be reasonably efficient to use for a wide range of applications. Gleam1 is my new language of choice for writing internal tools, daemons and backend::frontend code. You too can try it at tour.gleam.run.
Haskell2
I have been a student of Haskell1 for many years. Not professionally, but as a hobby. Whenever I needed a custom tool, I tried to use it as a vehicle for learning Haskell1 and a few tools were written :) I learned a lot and fell in love with declarative and functional programming. Haskell1 never became my default language, it almost never does that for anyone, but everyone learns a lot from it. I took these lessons and applied them in my professional “Big Data” work with MapReduce pipelines, SQL and Python3.
Gleam1
Gleam1 is a new functional language targeting Erlang’s Beam4 Virtual Machine and also JavaScript Runtimes (Node, Bun4, Browser JS engines). The feature-set and syntax is the best of Haskell1, with a sprinkle of Ocaml practical impurity and backed by power of entire Beam4 ecosystem. The language is immutable, impure, static, and eager - quite a shift from Haskell1’s immutable, pure, static and lazy!! It comes with an amazing toolchain which includes builder, very fast compiler with amazing error messages, LSP5 and great documentation.
Beam4 VM
Beam4 Virtual Machine was built for running high-availability massively multi-threaded (implicit mix of POSIX and green threads) functional software. Erlang was the original language for which Beam4 was build, but nowadays the other members of big-three are Elixir and Beam4. There is full module interoperability between libraries from each of these languages and a robust FFI6 for C/C++/others. Beam4 is behind highly scalable services such as WhatsApp, Discord, RabbitMQ and Pinterest.
Learning Gleam1 Basics
As a vehicle to learn the language basics, Beam4 features, standard library, build system and runtime characteristics, I wrote a micro-benchmark representative of workloads I tend to run. Nothing super complex, but exercising enough of the runtime to reveal it’s good and bad sides.
The micro-benchmark:
- streaming read of 16MiB chunks of 1GiB file to exercise system IO interface, memory allocation, GC
- hand-off chunks to worker-pool (size limited to number of hardware contexts) exercising IPC overhead
- each worker builds a histogram of values exercising (in)efficiency of the language for binary data handling
- workers send results back to main thread exercising IPC latency
Benchmarks
The Gleam1 benchmark implementation was structured as a shared library and I crafted two main() functions around it. One for running on Beam4 VM and utilizing a crew worker-pool, and another for running on Bun4 (JavaScript engine) single threaded. Additionally, I implemented a C-native (NFI6) inner function for calculating the chunk’s histogram and set it up to be optionally used by workers.
To compare Beam against other languages of interest to me, I implemented the same micro-benchmark in Zig7, Haskell1 and two Python3 implementations: one with interpreter based histogram and another using str.translate() to produce histogram of the entire chunk (which Python3 implements in C).
Note that these implementations are fully serialized as a loop of: {read,
process}, while my Gleam Beam implementation pipelines read with process
even in the single worker mode.
The benchmark was run on a 1GiB random data file primed into Linux kernel cache to avoid measuring my disk IO speed/latency. Machine used is a 32GiB ram box running GNU/Debian ‘stable’ 13.6 Trixie with Linux kernel 6.12 on an Intel i7-4790K with four physical cores and the worker pool fixed at four (4) as not to skew cost reporting due to hyper-threading.
Here are the results from shortest wall_sec time to longest: [chart]
| implementation | wall_sec | cpu_sec | cpu_num | max_MiB | binary_MiB | runtime details |
|---|---|---|---|---|---|---|
| gleam-c-th | 0.61 | 1.64 | 2.69 | 163 | 0.45 | Gleam 1.18 on Beam VM 29.0, worker-pool of 4, C histogram |
| zig | 0.76 | 0.75 | 0.99 | 17 | 3.72 | Zig 0.16 |
| gleam-c-1t | 0.85 | 1.48 | 1.74 | 154 | 0.45 | Gleam 1.18 on Beam VM 29.0, worker-pool of 1, C histogram |
| gleam-native-th | 6.67 | 26.28 | 3.94 | 306 | 0.45 | Gleam 1.18 on Beam VM 29.0, worker-pool of 4, native histogram |
| py-c | 9.29 | 9.20 | 0.99 | 58 | 0.00 | Python 3.14, str.translate() C histogram |
| gleam-native-1t | 24.86 | 25.11 | 1.01 | 161 | 0.45 | Gleam 1.18 on Beam VM 29.0, worker-pool of 1, native histogram |
| haskell | 25.30 | 25.05 | 0.99 | 61 | 14.07 | GHC 9.14 |
| gleam-bun | 111.42 | 117.0 | 1.05 | 168 | 75.96 | Gleam 1.18 on Bun 1.3.14 |
| py-native | 112.50 | 111.4 | 0.99 | 42 | 0.00 | Python 3.14, native histogram |
Gleam1: functional, impure, static, immutable, eager
Multi-threaded Gleam1 with C NFI6 histogram function is the absolute fastest beating Zig. The cpu_sec is higher than Zig by a factor of only 2x which is impressive! As project parallelism grows and locking and contention becomes a significant factor, Gleam should allow writing highly efficient and parallel applications much easier.
The native Gleam histogram version with full parallelism is not too far behind and keeps up respectably by loading all cores with native Gleam code.
To control for max_MiB I peppered the Beam implementations with explicit GC() to ensure runtimes show least inflated max_MiB results. With C histogram versions being so fast, it was not possible to utilize more than 2.69 cores, which also influenced low max memory footprint. The slowest native version shows max memory increase proportional to loaded cores, which is consistent with expectation of more in-flight data which can’t be GC-ed.
Gleam’s1 single-threaded implementation using the Bun4 runtime was completely non-competitive for this type of (server) workload. It excels at client-side in-browser lightweight workloads.
Zig7: imperative, impure, static, mutable, eager
Zig is a modern take on C with more safety and can be considered running on bare metal. The benchmark shows most efficient execution and lowest memory footprint barely bigger than chunk size buffer. Disclosure: the Zig implementation was made with help of AI. I’m following Zig, but I’m not a a practitioner :)
Haskell2: functional, pure, static, immutable, lazy
Haskell2 was surprisingly bad. I really needed AI to improve it from original implementation which suffered an even 5x slower performance due to inefficient immutable data handling.
Python3: imperative, impure, dynamic, mutable, eager
Python3 interpreter based histogram implementation was very slow, but using str.translate() to implement histogram processing on a chunk made it a good performer. The magic is in str.translate() being implemented in Python3 runtime as a C-function, so processing a chunk was done in bare-metal wrapped in py IO for reads. It still ended a bit slower than gleam-native-ht!
Winner is …
For me, the features of Gleam1 language and Beam4 VM, combined with effortless and very safe parallelism constructs (compared to other languages) allow for trivially simple and very linear speedups on SMP systems. When workloads are dominated by IO, latency, and blocking, Gleam1 is much simpler than async approaches and scales better. It’s raw data processing speed is impressive and when absolutely needed, inner loops can be implemented in C/Zig7 for bare-metal competitive processing costs without the complexity.
Language Features
My programming journey of over 30 years progressed through following languages: Basic, Pascal, Assembly, RPL, C, Java, C++, Perl, Python3, SQL8, Haskell1 and now Gleam1. I’m very excited about the future and feel we are programming in exciting times. Many languages are now adopting or making possible programming in functional style (tail recursion and functions being first-class values) even if they naturally skew towards imperative paradigms.
Different languages use different combinations of features to structure their execution. I mentioned following language features above - here is a bit more context:
| Feature | Summary | Impact |
|---|---|---|
| Functional | describes how data is transformed | focuses on evaluating functions to produce values and passing functions as data |
| Imperative | describes how to perform a sequence of tasks | focuses on changing state |
| Pure | Results depend only on inputs | no side-effects and entire classes of complex bugs impossible, but requires use of more complex IO semantics |
| Impure | Allows side effects (global state mutation, IO) | pure vs impure is responsibility of dev, but allows normal IO semantics |
| Static | Types checked at compile time | entire classes of errors impossible, compiler enforces better safer code |
| Dynamic | Types resolved at runtime (strong or weak) | runtime errors and runtime cost, “but flexible” |
| Immutable | data cannot be changed after creation | changing data requires more memory and memory bandwidth, but sharing data between workers is easy and safe |
| Mutable | data can be changed in-place | lowest memory and memory bandwidth requirements, but sharing between workers requires complex locking and contention |
| Lazy | Expressions are evaluated as values are needed | values produced only when accessed, difficult to debug and can cause resource leaks, but simpler code in many cases |
| Eager | Expressions are evaluated immediately | more verbose code, but generators/IO_iterators can still be used to implement laziness where needed |
Appendix
Disclaimer: I’m not trying to make this a serious-benchmark project, just a reasonably representative but simple micro-benchmark.
-
Gleam: a new functional programming language for Beam Virtual Machine. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Haskell: an academic research programming language exploring frontiers of functional programming. ↩︎ ↩︎ ↩︎
-
Python: A very popular dynamic interpreted “glue” programming language. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Beam VM: a virtual machine for efficiently running Erlang, Elixir and Gleam functional programs with high availability and massive multi-threading. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
LSP: Language Server Protocol - a mechanism for programming language toolchain to communicate auto-complete, syntax errors, diagnostic messages, lint, hover documentation, etc, in real-time to one’s editor of choice. ↩︎
-
FFI: Foreign Function Interface - in Beam called NFI. ↩︎ ↩︎ ↩︎
-
Zig: A successor to C programming language with more safety and a great toolchain. ↩︎ ↩︎ ↩︎
-
SQL: Structured Query Language: declarative language for manipulating large data. ↩︎