- No ad-hoc polymorphism (apart from function overloading IIRC) means no standard way of defining how things work. There are not many conventions yet in place so you won’t know if your library supports eg JSON deserialization for its types
- Coupled with a lack of macros, this means you have to implement even most basic functionality like JSON (de)serialization yourself - even for stdlib and most popular libs’ structs
- When looking on how to access the file system, I learned the stdlib does not provide fs access as the API couldn’t be shared between the JS and Erlang targets. The most popular fs package for erlang target didn’t look of high quality at all. Something so basic and important.
- This made me realise that in contrast to elixir which not only runs on the BEAM („Erlang“) but also runs with seamless Erlang interop, Gleam doesn’t have access to most of the Erlang / Elixir ecosystem out of the box.
There are many things I liked, like the algebraic data types, the Result and Option types, pattern matching with destructuring. Which made me realize what I really want is Rust. My ways lead to Rust, I guess.
Gleam has access to the entire ecosystem out of the box, because all languages on the BEAM interoperate with one another. For example, here's a function inside the module for gleam_otp's static supervisor:
@external(erlang, "supervisor", "start_link")
fn erlang_start_link(
module: Atom,
args: #(ErlangStartFlags, List(ErlangChildSpec)),
) -> Result(Pid, Dynamic)
As another example, I chose a package[0] at random that implements bindings to the Elixir package blake2[1]. @external(erlang, "Elixir.Blake2", "hash2b")
pub fn hash2b(message m: BitArray, output_size output_size: Int) -> BitArray
@external(erlang, "Elixir.Blake2", "hash2b")
pub fn hash2b_secret(
message m: BitArray,
output_size output_size: Int,
secret_key secret_key: BitArray,
) -> BitArray
It's ok if you don't vibe with Gleam – no ad-hoc poly and no macros are usually dealbreakers for certain types of developer – but it's wrong to say you can't lean on the wider BEAM ecosystem![0]: https://github.com/sisou/nimiq_gleam/blob/main/gblake2/src/g...
Hayleigh, when I asked on the discord about how to solve my JSON problem in order to get structured logging working, you replied that I’m the first one to ask about this.
Now reading this: > It's ok if you don't vibe with Gleam – no ad-hoc poly and no macros are usually dealbreakers for certain types of developer
Certainly makes me even more feel like gatekeeping.
Why do you feel like a gatekeeper? Your opinion is valid, it's just that the interop statement was wrong.
For Elixir I saw a simple distributed job scheduler - it was dead simple in code and was ripped, because it didn’t require maintenance for ~8 years just working without issue and people who knew anything about it left company or switched part of company and acted as they forgot everything.
The other example is medium sized (in terms of features and code) web app - maintained by <30 people now, delivering more than 800 people at the other company, no stress, no issues and with great DX because of the BEAM (other company is drowning in JVM based nano-services).
- Have to deploy product XYZ (because we don't write everything from scratch)
- Need to extend said product
- Use one of the official SDKs, because we aren't yak shaving for new platforms
Thus that is how we end up using the languages we kind of complain about.
To be fair, languages like Elixir and Gleam do exist, because too many complain about Erlang, which me with my Prolog background see no issues with.
Honestly, and I realize that this might get me a bit of flack here and that’s obviously fine, but I find type systems start losing utility with distributed applications. Ultimately everything being sent over the wire is just bits. The wire doesn’t care about monads or integers or characters or strings or functors, just 1’s and 0’s, and ultimately I feel like imposing a type system can often get in the way more than it helps. There’s so much weirdness and uncertainty associated with stuff going over the wire, and pretty types often don’t really capture that.
I haven’t tried Gleam yet, and I will give it a go, and it’s entirely possible it will change my opinion on this, so I am willing to have my mind changed.
So it’s hard to see how types get in the way instead of being the ultimate toolset for shaping distributed communication protocols.
Types naively used can fall apart pretty easily. Suppose you have some data being sent in three chunks. Suppose you get chunk 1 and chunk 3 but chunk 2 arrives corrupted for whatever reason. What do you do? Do you reject the entire object since it doesn’t conform to the type spec? Maybe you do, maybe you don’t, or maybe you structure the type around it to handle that.
But let’s dissect that last suggestion; suppose I do modify the type to encode that. Suddenly pretty much every field more or less just because Maybe/Optional. Once everything is Optional, you don’t really have a “type” anymore, you have a a runtime check of the type everywhere. This isn’t radically different than regular dynamic typing.
There are more elaborate type systems that do encode these things better like session types, and I should clarify that I don’t think that those get in the way. I just think that stuff like the C type system or HM type systems stop being useful, because these type systems don’t have the best way to encode the non-determinism of distributed stuff.
You can of course ameliorate this somewhat with higher level protocols like HTTP, and once you get to that level types do map pretty well and you should use them. I just have mixed feelings for low-level network stuff.
Of course it’s different. You have a type that accurately reflects your domain/data model. Doing that helps to ensure you know to implement the necessary runtime checks, correctly. It can also help you avoid implementing a lot of superfluous runtime checks for conditions you don’t expect to handle (and to treat those conditions as invariant violations instead).
For things that are invariants, that’s also trivial to check against with `if(!isValid(obj)) throw Error`.
I like HM type systems a lot. I’ve given talks on type systems, I was working on trying to extend type systems to deal with these particular problems in grad school. This isn’t meant to a statements on types entirely. I am arguing that most systems don’t encode for a lot of uncertainty that you find when going over the network.
For ensuring bits don't get lost, you use protocols like TCP. For ensuring they don't silently flip on you, you use ECC.
Complaining that static types don't guard you against lost packets and bit flips is missing the point.
Again, it’s not that I have an issue with static types “not protecting you”, I am saying that you have to encode for this uncertainty regardless of the language you use. The way you typically encode for that uncertainty is to use an algebraic data type like Maybe or Optional. Checking against a Maybe for every field ends up being the same checks you would be doing with a dynamic language.
I don’t really feel the need to list out my full resume, but I do think it is very likely that I understand type systems better than you do.
Sure thing. Unless dev forgets to do (some of) these checks, or some code downstream changes and upstream checks become gibberish or insufficient.
Gleam is still new to me, but my experience writing parsers in Haskell and handling error cases succinctly through functors was such a pleasant departure from my experiences in languages that lack typeclasses, higher-kinded types, and the abstractions they allow.
The program flowed happily through my Eithers until it encountered an error, at which point that was raised with a nice summary.
Part of that was GHC extensions though they could easily be translated into boilerplate, and that only had to be done once per class.
Gleam will likely never live to that level of programmer joy; what excites me is that it’s trying to bring some of it to BEAM.
It’s more than likely your knowledge of type systems far exceeds mine—I’m frankly not the theory type. My love for them comes from having written code both ways, in C, Python, Lisp, and Haskell. Haskell’s types were such a boon, and it’s not the HM inference at all.
Actually Gleam somewhat shares this view, it doesn't pretend that you can do typesafe distributed message passing (and it doesn't fall into the decades-running trap of trying to solve this). Distributed computing in Gleam would involve handling dynamic messages the same way handling any other response from outside the system is done.
This is a bit more boilerplate-y but imo it's preferable to the other two options of pretending its type safe or not existing.
IMO the right approach is just to parse everything into a known type at the point of ingress, and from there you can just deal with your language's native data structures.
Once you add distribution you have to encode for the fact that the network is terrible.
You absolutely can parse at ingress, but then there are issues with that. If the data you got is 3/4 good, but one field is corrupted, do you reject everything? Sometimes, but often Probably not, network calls are too expensive, so you encode that into the type with a Maybe. But of course any field could be corrupt so you have to encode lots of fields as Maybes. Suddenly you have reinvented dynamic typing but it’s LARPing as a static type system.
I get what your saying, but can't you have the same issue if instead you have 3 local threads that you need to get the objects from, one can throw an exception and you only receive 2, same problem
When you have to deal with large amounts of uncertainty, static types often reduce to a bunch of optionals, forcing you to null check every field. This is what you end up having to do with dynamic typing as well.
I don’t think types buy you much in cases with extreme uncertainty, and I think they create noise as a result.
It’s a potentially similar issue with threads as well, especially if you’re not sharing data between them, which has similar issues as a distributed app.
A difference is that it’s much cheaper to do retries within a single process compared to doing it over a network, so if something gets borked locally then a retry is (comparatively) free.
On one end, you write / generate / assume a deserialisator that checks whether incoming data satisfies all required invariants, eg all fields are present. On the other end, you specify a type that has all the required fields in required format.
If deserialisation fails to satisfy type requirements, it produces an error which you can handle by eg falling back to a different type, rejecting operation or re-requesting data.
If deserialisation doesn't fail – hooray, now you don't have to worry about uncertainty.
The important thing here is that uncertainty is contained in a very specific place. It's an uncertainty barrier, if you wish: before it there's raw data, after it it's either an error or valid data.
If you don't have a strict barrier like that – every place in the program has to deal with uncertainty.
So it's not necessarily about dynamic / static. It's about being able to set barriers that narrow down uncertainty, and growing number of assumptions. The good thing about ergonomic typing system is that it allows you to offload these assumptions from your mind by encoding them in the types and let compiler worry about it.
It's basically automatization of assumptions book keeping.
The "its only bits" thing makes no sense in the world of types. In the end its machine code, that humans never (in practice) write or read.
Despite of the suspicion, Gleam provides a better and elegant syntax for those who are not familiar with Erlang or functional programming languages, which I loved most.
I'm paying keen attention to Gleam to see if it can provide a robust development experience in this way, in the longer term.
import gleam/io
pub fn main() {
io.println("hello, friend!")
}
becomes this Raku say “hello, friend!”
well maybe you really want to have a main() so you can pass in name from the command line #!/usr/bin/env raku
sub MAIN($name) {
say "hello, $name!”
}It's confusing too. Is Gleam suitable for distributed computing like Elixir/Erlang on BEAM? Would that answer change if I compile it to JS?
My main friction point is that the Int type maps to different concepts in erlang and js
In erlang it's a arbitrary precision Int
In js it the js number type, which is a 64bit float iirc.
Also recursion can hit limits way sooner in js.
For me, my code rarely ran in both js and erlang. But could be skillissue
If you compile it to JS, then the guarantees change to JS's guarantees.
Personally I've felt that the JS target is a big plus and hasn't detracted from Gleam. Writing a full stack app with both sides being in Gleam and sharing common code is something I've enjoyed a lot. The most visible impact is that there's no target specific functions in the stdlib or the language itself, so Erlang related things are in gleam_erlang and gleam_otp, and e.g. filesystem access is a package instead of being in the stdlib. If you're just into Erlang, you don't need to interact with the JS target at all.