Elixir: Na één slok wil je nooit meer terug

This blog post is unfortunately not available in English.

Elixir is een dynamische, functionele programmeertaal, gedesigned om schaalbare en onderhoudbare applicaties mee te bouwen.
Er zijn twee redenen om over te stappen naar Elixir van de huidige taal waar je mee bezig bent:

  1. Elixir is razendsnel, extreem schaalbaar en foutbestendig.
  2. Elixir is desalniettemin heel erg leesbaar, uitbreidbaar en ook nog te snappen als je over een half jaar je oude code weer tegen komt.

In dit artikel vertel ik kort over de designkeuzes van Elixir, en waarom Functioneel + Actor-Model Programmeren veel problemen oplossen waar we in Object-Georienteerd-land mee te kampen hebben.

Concurrency strookt met Mutability

Computers zijn constant in ontwikkeling. Een paar decennia geleden was geheugen nog een hulpmiddel waar erg weinig van beschikbaar was in een computer. De programmeertalen destijds waren er dan ook heel erg gemaakt om een heel klein stukje van het computergeheugen te gebruiken, en elke keer dat ene stukje te overschrijven (muteren).

Tegenwoordig hebben we dit probleem niet meer: Geheugen is erg goedkoop. Waar we nu mee zitten is de omgekeerde wereld; CPU’s worden niet meer sneller. In plaats daarvan stoppen we nu meerdere CPU-cores in onze computers. Dit betekent dat nieuwe software alleen sneller kan worden als er goed gebruik gemaakt wordt van deze nieuwe cores.

Deze twee ideeën stroken echter met elkaar; Data-synchronisatie met meerdere cores is een moeilijk probleem, en dit gaat vaak fout. Sommige talen hebben besloten om dit probleem te laten ‘verdwijnen’ door een Global Interpreter Lock toe te voegen. Dit betekent dat het gebruik van meerdere cores meteen onmogelijk wordt. Ook wanneer er geen GIL is, blijft het ingewikkeld om data te delen. Er is echter een beter alternatief: Immutability.

Immutability

Op het moment dat je data niet deelt, maar in plaats daarvan altijd een kopie van de eerdere data teruggeeft, dan is er geen sprake van dit probleem.

Een bijkomend voordeel van data immutability, is dat het altijd duidelijk is wanneer er iets wordt aangepast (want dit betekent dat je het aan-te-passen blok data als input neemt en verderop retourneert). In een OOP-taal is het vaak heel moeilijk te zien wanneer ergens nou wel of niet de staat van een object wordt aangepast. Deze implicietheid zorgt snel voor moeilijk te lezen/begrijpen code. Een taal als Elixir heeft hier dus geen last van.

Pattern-matching

Een bijkomend voordeel van immutability, is dat er maar één manier is om een bepaald soort data te representateren. Dit betekent dat het mogelijk is om veel vergelijkingen tussen data in O(1) uit te voeren. (In OOP-talen dien je elke keer handmatig alle velden van een object bij langs te gaan, wil je zeker weten dat de representaties overeenkomen)

Ook is het hierdoor mogelijk om zogenaamd pattern-matching te gebruiken: Meerdere functie-varianten kunnen geschreven worden die aangeven wat voor data-structuur ze als input verwachten. In Elixir gebeurt functie-dispatching intern dmv. een trie, wat in de praktijk vele malen sneller is dan method-delegation in OOP-talen.

Actor-Model

Code in Elixir wordt uitgevoerd op zogenaamde Processen. Dit zijn hele lichtgewicht threads die door de VM worden geregeld en automatisch over de CPU cores worden verdeeld. Op een hedendaagse computer kun je zonder problemen duizenden van deze processen starten en gebruiken.

Elk proces heeft zijn eigen state. De enige manier hoe processen informatie met elkaar kunnen uitwisselen, is door berichten heen-en-weer te sturen. Dit betekent dat processen daadwerkelijk tegelijk (concurrent) uitgevoerd kunnen worden.

Dat elk proces zijn eigen heap heeft betekent ook dat garbage-collection simpel is t.o.v. andere talen, wat problemen zoals Stop-the-world voorkomt.

Supervision: Let It Crash

Dat alle data ge-encapsuleerd is in processen, betekent ook dat wanneer één van deze processen faalt, de rest van het programma zonder problemen blijft werken.

In de praktijk zijn er altijd uitzonderingsgevallen waar je niet aan hebt kunnen denken. Situaties veranderen: error recovery is belangrijker dan error handling. Elixir geeft je de mogelijkheid om processen te linken in management-structuren genaamd supervision trees, om processen die gecrashed zijn op een slimme manier te herstarten, wanneer nodig.

Dit zorgt er voor dat de systemen waar Elixir (en Erlang, waar Elixir op gebouwd is) extreem stabiel zijn. Meer dan 50% van de Telecom-platformen op dit moment draait op dezelfde infrastructuur: De reden dat je je telefoon kunt gebruiken betekent dat het goed werkt!

In de meeste talen zou je, wanneer je een webserver wilde maken, één webserver hebben die een hele hoop connecties aangaat. In Elixir maak je voor elke request zijn eigen webserver met een persistent connection. Dit betekent ook hier dat wanneer één deel van je programma crasht, de rest vrolijk blijft werken. Dit is een heel groot voordeel!

De supervision-trees en actor-model functionaliteit zitten allemaal ingebakken in de taal. Het is ook mogelijk om, zonder extra werk, processen die op verschillende computers draaien met elkaar te laten communiceren en op deze manier heel eenvoudig je systeem te schalen.

Code-structuur

Functioneel programmeren is anders dan Object-georienteerd programmeren. Dit betekent dat een hele andere kijk tegen hoe je problemen oplost nodig is.

Het grootste verschil, is dat we bij Functioneel Programmeren niet bezig zijn met het verstoppen van data, maar met het transformeren van data.

In een functionele taal zijn deze transformaties in staat expliciet. Dit zorgt er voor dat veel design-patterns die men in OOP eerst dient te leren (en vaak verkeerd toepast) zichzelf automatisch toepassen in AM/FP land:

OOP-patterns vs FP-patterns

Daarnaast is documentatie een first-class-citizen in Elixir-code(tests die in de documentatie staan worden zelfs automatisch voor je uitgevoerd!), en is de documentatie van de Standard Library en de beginnershandleiding(en) uitmuntend.

Het schrijven van code in Elixir kost niet meer moeite/tijd dan om vergelijkbare programma’s te schrijven in talen zoals Ruby/Java/Python. Wanneer je het gevoel hebt dat je te veel aan het typen bent kun je waarschijnlijk hergebruikte functionaliteit in zijn eigen functie(s) of module stoppen. Ook is het mogelijk om de fantastische ingebouwde metaprogramming-mogelijkheden van Elixir te gebruiken om de functionaliteiten van de taal verder uit te breiden.

Het is ook absoluut niet nodig om het wiel opnieuw uit te vinden: De Hex.pm package manager heeft meer dan 2000+ open-source projecten die functionaliteit voor je kunnen bieden. En Elixir-code kan altijd terugvallen op de meer dan 25 jaar dat er al Erlang-applicaties bestaan, en deze in de code aanroepen.


Elixir is een sprankelende nieuwe taal, gebouwd op een rotsvaste fundering die orkanen kan doorstaan. In Elixir zijn een aantal designkeuzes gemaakt die anders zijn van de meeste andere talen waar men mogelijk aan gewend is. Deze keuzes zorgen er voor dat de taal extreem snel, schaalbaar en foutbestendig is.

Daarnaast zorgen Elixir’s syntax en opzet er voor dat programma’s eenvoudiger zijn om ook op de lange termijn te onderhouden.

Ik hoop dat je de taal uitprobeert en net zo einthausiast wordt als wij. Zelf heb ik één slok gehad, en nu wil ik nooit meer terug.