This is an attempt to wrap my head around using the Phoenix web
framework for a small project. However, it got too long so I broke it
into two parts. This is part 1 and just talks about the Elixir language,
part 2 is PhoenixForCynicalCurmudgeons but that is
still WIP. However, I realized that I have a number of attitude
issues that make Phoenix hard to start getting into, so I’m going
to attempt to write about it in a way that makes sense to me.
My issues are:
- I don’t actually know Erlang too well in practice
- I don’t actually know web programming too well in practice
- I don’t actually like web programming much
- I’ve spent the last 10 years being the go-to person to fix every
random technical thing that could screw up, and am thus something of a
pessimist and a control freak - The world is burning down around us and none of us can do anything
about it
So, if you share some of these issues, maybe this doc will be useful
to you. This will not teach you Elixir or Phoenix, but it may help you
figure out how to think about them. This is a supplement to the
official
docs, not a replacement, so it won’t try to cover the things that
those docs explain well.
Disclaimer: As far as I know this is correct, but there’s probably
details I’m missing or misinterpreting. Don’t take this as gospel. I am
not an expert, I am merely a determined idiot.
Last updated in August 2023. It uses Elixir 1.14, Erlang/OTP 25 and
Phoenix 1.7.7.
Really what I want to get a handle on is the web framework Phoenix.
But Phoenix is written in the Elixir programming language, so to tackle
Phoenix, we must tackle Elixir.
I’m not going to spend too long singing the praises of The Erlang/OTP
Platform, I assume if you’re here then you already know enough to be
interested. Long story short, it gives you multiprocessing via
communicating independent processes, makes almost all state immutable so
these independent processes can only talk with each other via
channels, and gives you bunches of tools for handling the failure and
restarting of processes, as well as inspecting and changing code at
runtime. It runs on a VM called BEAM, because for various reasons the
conventional Unix process model can’t actually give the same level of
robustness and introspection that you can get with a sandboxed virtual
machine. Erlang itself is a wild programming language made in the late
80’s that started with Prolog with a sprinkling of Lisp here and there
and turned it into a language like nothing else on this earth. Elixir is
a much newer and more comfy-looking programming language that started in
2012 and also compiles to the BEAM VM, offering good compatibility with
Erlang code.
I love Erlang but have never really used it In Anger; the biggest thing I’ve
written with it is a card game. I have dipped in and out of it many
times over the years, but most of the time I just don’t have a lot of
use cases where it’s obviously the best choice over something else. It
has always seemed like the ideal tool for non-trivial web applications,
but I never really had a need to write that. But I recently had an idea
I want want to try to write for an actual web service that’s not a
static generated site or a tiny mono-purpose API, so I figured I’d take
a good stab at using Erlang or Elixir for it.
Elixir at first blush looks like they just took Erlang and re-skinned
it to look like Ruby. However, Elixir offers some modest but meaningful
functional upgrades over Erlang:
- Nested modules. Erlang has a flat module namespace, you can make a
module namedfoo
but you can’t put a module named
bar
into it and getfoo/bar
or
foo.bar
. This is Fine but not ideal, you generally end up
faking the nesting and making a module namedfoo_bar
anyway. Erlang doesn’t care, but it also doesn’t help you. - Better strings. Erlang strings are by default a linked list of
characters, which probably made a lot more sense for a telecoms
infrastructure language in the late 1980’s than it does for a web
language in the 2020’s. This still actually works half decently because
a lot of I/O can be done with lists of substrings rather than
creating/modifying existing strings, but it’s still not ideal. But
Erlang also has bit-strings which are immutable arrays of arbitrary
bytes, and you can stuff ASCII or UTF-8 data into those and use them as
strings too. In Elixir the defaults are flipped: strings are immutable
arrays by default, and if you need to talk to Erlang code that expects
the linked-list type of string you can create them. It still keeps the
scatter-list-y I/O model though, so constructing a new string out of a
bunch of pieces with a format function or such won’t allocate a new
buffer and copy the strings into it, it will just produce a list of
packed string fragments. Elixir strings also assume UTF-8 by default;
Erlang bit-strings assume no particular encoding. - Nicer structure syntax. Erlang is sort of weird ’cause for the
longest time structs were not first-class objects, they were just tuples
with macros for creating and accessing them. Erlang is a very dynamic
language that tends to say that abstraction is done by functions and
interfaces, and if you need to rummage around in the guts of your data
structures then you should. However, its creators really didn’t want to
add hashtables everywhere as a general-purpose structure the way that
Python and Ruby do, so they just sat and mulled on the problem. They did
eventually add real structures/records, which are much nicer wrappers
around tuples, but they’re still a little tacked-on. Elixir’s maps are
basically the same as Erlang’s structures (records, whatever), but more
convenient. - Bigger stdlib. Erlang’s stdlib is pretty nice, Elixir’s adds to it.
’Nuff said. - Better macros. Erlang has actually fairly powerful macros but they
are, frankly, a cleaned up C preprocessor. You can define constants,
include files, do some basic ifdef stuff, shit like that. You
can get significantly fancier, but it’s pretty clear that
you’re usually not intended to; the reference docs barely tell you
anything about it.
However, there’s also a bunch of cognitive bumps for me to overcome
when trying to learn Elixir:
- All the syntactic sugar. Elixir looks like Ruby, which is quite
loosey-goosey and malleable, and Elixir is even more malleable. There’s
a lot of bits in the first half of the tutorial that say “you
can also write it like this…” and gives you some uncertain thing with
less punctuation involved At first glance, it feels like it
tries excessively hard to make things pretty rather than good, and the
way Phoenix writes code makes that worse. It’s easy to feel like it’s
all just sugar rather than content. - Bigger language, period. Erlang is very appealingly simple; there’s
a small number of constructs that then fit together pretty well.
Sometimes it’s a little clunky, but if you look at some Erlang code then
there’s not much fanciness outside of how channels and processes
interact; it generally just does what it says it does. Elixir has a lot
more going on at many different levels; why do we need that? - Big complicated project structure. You can make an Elixir
project that’s just a single script or a directory with a small
collection of files, but the docs prefer to plunge you into its build
toolmix
, designing an OTP application, how
GenServer
works, etc. It’s all significantly more
complicated to pick up and get going withmix
than with
cargo
,zig
or other tools I’m more familiar
with. Some of it like OTP andGenServer
are already
familiar from Erlang, but in that case what does Elixir add over just
using Erlang again? - Tries fairly hard to look hip. The presentation for both Elixir and
Phoenix is quite shiny, it has lots of very friendly introductions that
tell you how great it is, all the features it has, and how it’s
totally worth all this extra complexity to get into it and do everything
with its awesome new paradigm. This makes me automatically distrust it.
It’s honestly not that bad, and in my more forgiving moments I
ha