I view "must have static types/compile time checking" types as a bit reductionist. It's like saying "all food must be seasoned with salt." Salt's a good seasoner. For some meals, it's a must have. For others, it's a nice to have. And for others, it actually detracts. Compile time checking fits in the larger context of what programming in a given language is all about. Syntax, libraries, ecosystem, tools, execution semantics, runtime components. All of these compose for an end result. And typing plays a different role for each composition.
I've been doing Elixir off-and-on for about 1.5 years now. The community is great. If I personally, had to lobby for "next big thing", it would be having a real IDE. I did Smalltalk for 20 years. I want an IDE where I work in modules/functions, not files and do/end syntax. I want a formatter that is not a PHD paper on generalized layout. I want refactoring tools. Inline macro expansion visibility. If I were independently wealthy, I would work on just such a thing in my spare time.
ElixirLS and VSCode are OK, but so so limited really.
What makes Elixir a standout candidate for a killer IDE experience is it's simplicity. Simple execution models make the language easier to model, navigate, and so IDEs don't need parse engines that are constantly needing updating (I'm looking at you Kotlin/Compose). Elixir as a language eats its own dogfood. It's sad to me that the Xerox Parc folks exploited this so beautifully with Smalltalk, but Elixir is stuck in the file editor mode still. :(
The worst case for a dynamically-typed codebase is so very much worse in high-level languages.
Admittedly, the immutable data structures in Erlang/Elixir help offset this.
The case for types is very strong in Javascript though.
I also use Ruby for backend and TypeScript for frontend and see no problem with that. Some languages are well suited for dynamic. Sometimes not just due to the language but the community + culture around it. Ruby codebases are also very heavily test driven which helps compensate and the inherent flexibility/developer experience of Ruby can't be matched. Elixir being the only close match.
But it has not been my experience. Nor some of my peers. Despite a meh text editor, the Smalltalk IDEs (Smalltalk/V, VisualWorks, and VisualAge) were amazing. The original "refactoring" work done by John Brant and Don Roberts was pioneered at UIUC in these environments. Their adaptation to the different IDEs just improved them.
I remember sitting next to John and Don at OOPLSA and speculating that refactoring inside of Java (Eclipse was The New Editor at the time) should be superior in Eclipse vs Smalltalk because Java had more type information to work with. John and Don, both Smalltalk enthusiasts in those days, admitted they had anticipated that as well. And they did a bunch of work trying to port refactoring stuff to Eclipse. They were surprised to discover it wasn't so. But it's not typing vs not. It's simplicity vs not. When I asked if that meant that rising start Ruby would benefit from their refactoring work, they said that too was hard. Don explained that in a nut shell, it was the AST. Much of what tools/IDEs do is work with a model of the language. The Smalltalk AST had 15 polymorphic objects to represent its language model. Ruby was 90+ and climbing at the time. Java was insane. Their take was that while typing might add some information, the combinatorial explosion of modeling the language just made tooling for the language more difficult. It was an interesting insight.
Elixir/Erlang has a language model that is even simpler than Smalltalk in some ways. The trickiest part is the macros.
The elixir project I was in had several dozen developers on it and specs were not enough. I always had to backtrack through several calls and/or run the code and dump the data and then capture the example data as a comment so future devs could know what was coming into the function. Not a great solution.
I know the author of Gleam is going full-time working on it, but I really know much about it or how it gets around the problems with statically typing message-passing. I'm perfectly happy working in dynamic languages and find Elixir's type hinting to be more than adequate.
I've been using Elixir since 2015 and can confidently say there is no use case I would choose Elixir for over PureScript for the BEAM. In practice my projects are Elixir + PureScript because we can have them interoperate, but there are zero technical reasons to choose Elixir over PureScript for BEAM work.
Edit:
`dialyzer` and similar tools are unusable for almost all sizes of projects in part because they're badly made, they're too lax and have zero useful abstraction properties. Making a spec generic, for example, is an exercise in futility, whereas an actual type system has no issues with expressing that very basic property.
Edit 2:
As an upside you also have PureScript for your frontend, so you can just write everything in the same language regardless of how much frontend work you expect to be doing. PureScript has great bindings and a great story around React (it actually fits better since it's a purely functional language, so things like "You can only do effects in `useEffect` actually are enforced and make sense) and also has its own frontend framework in Halogen which is very nice.
I think it might already mostly exist, it just isn't organized into a discipline.
What are people looking for that they might get from static types?
The difference in tooling support/IDE completions I get on practically any language that has types vs those that don't is just too drastic for me.
It's also so much less mental load to have to always remember the types in my head.
Another issue I've seen in dynamic languages is when people do try to document the types via comments or otherwise, but then this drifts from reality due to not being updated.
I actually tried elixir and this is one of the main things that turned me off it.
The reason for that is simple, the language works with modules and data pipes, not objects which means there's way less magic to parse
As for the burden of the mental model, I can’t really speak to that. In Elixir I usually try to think in data shapes or structures which can be matched on by function heads. I only think of types at the edges of the system.
- Ease of refactoring once the project got to a certain size.
- Preventing a lot of my stupid bugs (typos, passing the wrong type etc.).
- Quicker and easier understanding of argument and return types. The types are a form of documentation!
- Auto-completion and inline docs for library functions.
- Fewer tests needed, since everything the type system does has to otherwise be tested by a manually written test.
Just things anyone would tell you that appreciates static typing.
As for bugs, the research on this has always been mixed at best, I guess YMMV.
I’d personally argue that the structural typing in Elixir is more useful and practical than the types in most popular static languages like Java or TS.
Mostly I think it’s a wash where you get a little but give up some useful things too.
Typing systems exist on a spectrum: I constantly get type issues on Javascript and Python, refactoring is a nightmare without a ton of tests, while it was never a big issue in Elixir. Yes, its typing system is inadequate, and dialyzer isn't great, but in practice it is not that much of a problem. Pattern matching saves the day, and type errors found in rarely used code paths, what's the worst they can do? Crash the process? That's the least of our problems on the BEAM.
I've maintained a big data ingestion system that kept getting fed with bad, unforeseen data and it's never gone offline in the 3 years I've overseen its operation. If a bug causes your program to segfault or throw a NullPointerException, you will definitely want to have a strong typing system.
A type system also gives additional assurances when changing code that is used from many places. It's so nice to make a change and have additional confidence in it because the type system is happy with it.
I can't think of many bugs that I've seen that would have been prevented by a type system. I'd still like to have a type system though, it's sort of extra documentation within the code.
Having a type system in place makes minor refactoring possible in this nightmare scenario.
I have been in the situation of poorly written ruby codebases and I can tell you 100% that I would prefer to have a poorly written java codebase with its static types. I prefer ruby as a language but man when it's bad, it's terrible. Just trying to work out the intent of a function when multiple types are passed in as the same argument over the codebase is pure hell.
FB a few years ago announced they were going to work on it for WhatsApp but then it was indefinitely delayed. There’s also been a few other attempt, but I don’t believe anyone has succeeded.
I know Gleam exists but haven’t dug into it.
Anyone more in the know care to share?
https://elixir-lang.org/blog/2022/10/05/my-future-with-elixi...
In the meantime I don’t know what people have against using @spec. It’s a far more powerful type specification than the majority of static-typed languages out there
I previously worked with TypeScript which felt like a major productivity boost but I've ran into a few instances where Dialyzer came up with the most cryptic error message that took me at least an hour to figure out. It really sucks when that happens and I wish there were more high quality learning materials that would dive into Dialyzer.
I wish some LLM magic sauce could be applied to compiler and linter warnings in general that provide a context-appropriate plain language explanation, along with suggestions and automated fixes.
The other problem with specs is that they can become out of sync with function signatures.
It's not ergonomic enough (you end up writing function definitions twice).
And there's very little tooling to help automate the process: if you return the result of a function call, good luck figuring out the spec for that if that function is in a library somewhere
From reading that post, it sounds like Jose does not believe static typing is needed. But a nice to have to address a very narrow set of use cases that primarily would help for documentation, not bug mitigation.
Edit: Not sure why the downvotes, but the talking points by Jose are on his ElixirConf keynote
https://www.youtube.com/watch?v=Jf5Hsa1KOc8&t=408s
I'm not saying that types aren't a useful addition to Elixir, just that many people have wrong claims as to how they could be a useful addition to Elixir.
In terms of the typing itself, it's exactly what you get in all of PureScript, strict static typing with no `any` or the like. Using `Pinto`, the de facto OTP layer in PureScript your processes are typed, i.e. their `info` messages & state are typed, which means that they are all much more like strongly typed state machines than anything else.
You can see an example of a basic `gen_server` here:
The differences aren't very big in terms of what you'd expect to be doing. One small thing to note is that the `GenServer.call` expects a closure to be passed instead of having the split between `gen_server:call` & `handle_call`, removing the need for synchronizing two places for your messages being sent and handled.
0 - https://github.com/purerl/purerl
Edit:
As an upside you also have PureScript for your frontend, so you can just write everything in the same language regardless of how much frontend work you expect to be doing. PureScript has great bindings and a great story around React (it actually fits better since it's a purely functional language, so things like "You can only do effects in `useEffect`" actually are enforced and make sense) and also has its own frontend framework in Halogen which is very nice.