Hacker News new | ask | show | jobs
by hello_computer 770 days ago
Elixir is a combinatorics mess (circa 2020: two different string types, three different types of exception, return AND throw versions for almost every func, slow compiler without much in the way of compile-time checking, constant breakages over trivial renamings "just because", having to burrow into erlang errs half the time since many elixir funcs are just wrappers). Half the libraries we used were abandoned. I take every recommendation with a grain of salt. I think people got invested in the hype, and are trying to keep the sinking ship afloat.

Erlang is a different story. If you're interested in the BEAM, it isn't terrible. It has a few of the same problems, but the combinatorics are reduced.

3 comments

> Half the libraries we used were abandoned

People really need to unwarp their brains from how they judge libraries in Elixir compared to other ecosystems. Erlang is 30 years old. Elixir sits on top of that stability. Elixir will very likely never reach 2.0 because it doesn't need to. And if a 2.0 does come it will be simply to remove deprecated functionality.

Not having 12 major version releases per year means what you think are "abandoned" are actually stable and perfectly fine to use.

In Elixir, we don't really care about the last time a version was pushed. I regularly use and rely on libraries that haven't been touched in years because they don't need to be touched.

If the libraries worked, we wouldn't have had to play code archaeologist and find out that half our dependencies had been abandoned since 2017. Even something as simple as a JSON encoder--which is rock-solid in every other language I've used--had a number of bugs (this was four years ago, so memory is hazy, but it had something to do with ambiguity between arrays, lists, or tuples) Erlang is stable, but Elixir sure as hell isn't. Back in 2019, it seemed as though every point release brought a slew of breakages--and usually over the most trivial and pointless things, like adding an underscore to a built-in for "consistency". I've been developing for over 20 years, have used all of the mainstream languages, and Elixir was the absolute worst.
> Even something as simple as a JSON encoder--which is rock-solid in every other language I've used

JSON will be built into the next release of the BEAM: https://erlangforums.com/t/erlang-otp-27-0-rc3-released/3506...

How are there three different types of exception?

As for "two string types" maybe I'm not working on hard enough problems, but in ~4 years of Elixir I've never once needed to use a charlist. My understanding is that it's a backwards-compatibility thing from Erlang and I'm not even sure when I'd ever need to use it over a string.

raises, throws, & exits. rescue vs catch. as for binary vs charlist, some dependencies wanted binary, others wanted charlist. faced with the choice of re-write the dependency to use binaries, or wrap it with pre/post converters, we chose the latter. after the third or fourth global search-and-replace thanks to elixir's renaming of built-ins just for the hell of it, we re-wrote it in go, and never looked back.
We’ve had Elixir in production since 2017 and have not found any of the items you have mentioned to be issues.

- two different string types: You have undercounted (three types in Erlang, and Elixir adds a fourth), and suggested that something which is a non-issue for the vast majority of Elixir code. Most Elixir code deals with `String.t()` (`"string"`), which is an Erlang `binary()` type (`"string"` in Elixir, `<<"string">>` in Erlang) with a UTF-8 guarantee on top. A variant of the Erlang `binary()` type is `bitstring()` which uses `binary()` to efficiently store bits in interoperable ways. Code interacting directly with Erlang often needs to use `charlist()`, which is literally a list of integer values mapping to byte values (specified as `'charlist'` in Elixir and `"charlist"` in Erlang; most Elixir code would use `String.to_charlist(stringvar)` in the cases where required.

Compare and contrast this with the py2 to py3 string changes and the proliferation of string prefix types in py3 (https://docs.python.org/3/reference/lexical_analysis.html#li...).

- three different types of exception: true, but inherited from Erlang and the difference is mostly irrelevant. The three types are exceptions (these work pretty much as people expect), throws (non-local returns, see throw/catch in Ruby; these are more structured than `goto LABEL` or `break LABEL` for the most part), and process exits. In general, you only need to worry about exceptions in most code, and process exits only if you are writing something outside of your typical genserver.

- return AND throw versions for almost every func: trivially untrue, but also irrelevant. Elixir is more sparse than Ruby, but still comes more from the TIMTOWTDI approach so most libraries that offer bang versions usually do so in terms of one as the default version. That is, `Keyword.fetch!` effectively calls `Keyword.fetch` and throws an exception if the result is `:error`. It also doesn't affect you if you don’t use it. (Compare the fact that anyone who programs C++ is choosing a 30–40% subset of C++ because the language is too big and strange.)

- slow compiler without much in the way of compile-time checking: I disagree with "slow", even from a 2020 perspective, and "compile-time checking" is something that has only improved since you decided that Elixir wasn't for you. Even there, though, different people expect different things from compilers, and not every compiler is going to be the Elm compiler where you can more or less say that if it compiles it will run as intended. (I mean, the C++ compiler is both slow and provides compile time checks that don't improve the safety of your code.)

- constant breakages over trivial renamings "just because": false‡. Elixir 1.0 code will still compile (it may have deprecation warnings). To the best of my knowledge, nothing from 1.0 has been hard deprecated. ‡If you always compile `--warnings-as-errors`, then yes you will have to deal with the renaming. But that is a choice to turn that on, even though it is good practice.

- having to burrow into erlang errs half the time since many elixir funcs are just wrappers: not an issue in my experience, and I can only think of a handful of times where I have had the Erlang functions leak out in a way where I needed to look at Erlang error messages.

Elixir isn't suitable for everything, but frankly your list of so-called shortcomings is pure sour grapes.

I guess I will just have to stay sour with my sub-second compile times, actual compile-time type checks, stable naming of built-ins, single-binary deployments, and uniform error handling.
Better to say that whatever your resulting platform is (probably Go based on what you’ve said), it suits your constraints.

We've found that our constraints are better met for our most important project by Elixir. We are building something else in Go (mostly because it will be easier to find cheaper contractors to build these boring things once we have the first version done) and we still deliver a number of things in Ruby, and there's Typescript for some things. Use what works at the time you need it.

I personally find Go to be a tedious language and think it could use a fair number of DX improvements, ranging from adopting some form of Rust's terminal `?` sugar to just better recommendations on project structure and layout. It took far too long for Go to get generics, and the platform-specific build files being based on filename (e.g., `foo_aix.go`) is clever with all of the hatred I can put into that.

I have shipped software in ~40 different programming languages and distinct dialects over my career. Go does a number of things well, but some of the choices made are frankly bizarre. There are restrictions on how you can name things because of the platform bit and `internal/`, but then there's no recommendation on how to lay out a project otherwise. I’ll still use it any time over most other lower-level languages, but the language itself does not spark joy. The compiler is good and fast, but I have less confidence that I have built the right thing from it than when I use Rust.