JavaScript vs Elixir: surface-level differences

Craig Taub
8 min readOct 30, 2019

--

This came from a beginner-based Elixir talk I did recently.

The process of preparing for it really got me thinking about the differences between Elixir and JavaScript and where their strengths and weaknesses are. They are both good at different things and it all depends on perspective.

So I have compiled a small list of surface-level features (i.e. things which impact the day-to-day use of) which I will be touching on here from the Elixir perspective.

Mix

Mix is the build tool which comes with Elixir. However there are a couple of the things which give it the edge over NPM.

  1. Creating: mix includes an auto-generator which scaffolds your app for you. It produces a battle-tested and universally approved architecture, which is something you would need an additional tool for with JS (e.g Yeoman)
  2. Dependency management: It comes with HEX which is incredibly stable and comes from Erlang. JS still has controversy with the “NPM vs Yarn” question.
  3. Environment management: Mix comes with 3 environment concepts built-in, they are dev, test and prod. This is great and something missing from JS and you need an extra tool to assist with.
  4. Also compilation, formatting and testing. None of which come with NPM.

Another thing to note is that the commands are very consistent. In JS I have seen variations of the below:

  • npm run prettier
  • npm run format
  • npm run build-app

However with Mix the commands rarely change.

My feeling are that MIX is like many JS tools in 1. I.e.

Mix == npm + yeoman + babel + eslint + jest + dotenv

This kind of stability in tooling does real wonders for the ecosystem and consistency has an effect on adoption.

Basic syntax

Functions: Ruby-esk with def and end as well as snake_casing. There are no curly brackets which makes it look tidier. However I personally find it easier to differentiate (and use) curly brackets over def/end and therefore easier to read.

Key: Elixir (blue), JS (pink)

Expressions: Are delimited by a line break or a semicolon.

Elixir will be explicit about syntax issue however JS uses “automatic semicolon insertion”. See example below.

JS automatic semicolon insertion

With Elixir the clear and expressive syntax make it hard to introduce unexpected issues.

Types

There is a relatively short list of types in both languages, however the list is longer for Elixir.

Numbers in JS and Elixir are not entirely accurate as they are 64-bit floating point (i.e. 0.2+0.1 == 0.30000000000000004 in both). You could argue Symbols are like Atoms however that is not correct.

Many of Elixir types come with compile-time checks (eg. using an Atom instead of a String) but that is almost expected as the language is compiled.

The biggest issue with such a large list of types is that they all have functional and performance differences, so it is very important to understand them all quite well before knowing what to use and when.

Type - Nil

I thought this comparison was interesting.

Null is conceptually ubiquitous, it means the same thing in all langauges i.e. denote the absence of a value.

In both JS and Elixir you can use Null (or nil) as a falsey value in a boolean context.

There is a slight difference in the way JS and Elixir use it though:

Key: Elixir (blue), JS (pink)

This is because nil is an Atom (not a data type). However in JS it is a data type, even though nobody told the engine.

Guards

On the surface a Case expression might look like a switch in JS. See Below.

Key: Elixir (blue), JS (pink)

However switch cannot handle advanced types (e.g. Tuples, Lists, Structs) without further logic (as Elixir has its advanced pattern matching).

Also Elixir can catch raised exceptions inside the case whereas JS switch would need to be wrapped in a try/catch.

Function Clause:

Key: Elixir (blue), JS (pink)

The way you can lift logic into the head of a function with Elixir is really useful. JS has no way to do this and you would have to introduce a basic guard inside a function to achieve a similar thing.

Elixir has JS well and truly beaten with these features.

Function Overloading

Function overloading (also known as Arity) in Elixir is useful for recursion.

Elixir Arity

It is a really nice way to tie different code together.

JS can’t have the same function name twice in the same scope. There are 2 choices:

JS separate functions

First is to have a different function.

JS defensive guard

The second is to just use a guard inside the function. Not as concise as more code is required.

However this is because JS uses Variadic Functions by default. This means the total number of parameters are unknown and can be adjusted at the time the method is called (i.e. runtime). In many ways it can be as powerful as overloading, depending on the use-case.

Immutability

Elixir follows the “functional programming” paradigm. One of the ideas is that mutable types are open to monkey-patching are much more buggy.

In Elixir data structures don’t change. A value in a certain memory location can’t change just its reference or label. Changing the location is called “Rebinding”.

JS is not “functional” natively and uses pass-by-reference which can be slightly tricky.

Example 1: Remove item from an array

Key: Elixir (blue), JS (pink)

Here Elixir returns a new array, whereas JS would mutate the original array.

Example 2: Object comparison via equality operator

Key: Elixir (blue), JS (pink)

Elixir is able to easily do a comparison against a Struct key and value (using it’s pattern matching).

JS however requires more effort. You can see the first check is a “Reference equality check”. The operator will actually check the locations in memory. As both left and right create new locations in memory they will not match. In order to get “true” you must compare the reference itself.

The way to fix this is to do “Deep equality checks”. There are 2 ways to do this with JS:

  1. Stringify your object (like the example)
  2. Use an immutable type (e.g. Object.freeze), however this is not enforced natively

Chaining

Another idea with “functional programming” is to keep code “Stateless”. One of the techniques it advocates to help do this is called “composition”. Where functions are composed together to produce behaviour flows.

There are a couple of ways to do this. Both Elixir and JS can compose functions with Embedding. See example below.

Key: Elixir (blue), JS (pink)

The key problems are:

  • Bad readability (its not easy to read whats actually going on)
  • Hard to extend (to insert new behaviour to the events would you know where to add it?)
  • Call order (are things run inside-to-out or outside-to-in? It is easy to get this wrong).

There are some alternatives to this. JS has the prototype chain. This is due to its prototypal inheritance. However it only works with behaviour on the same type (i.e. you will not find a number-based method on the string prototype).

The best solution is to use the Pipe operator.

It fixes all the problems of the embedded code example and is easy to follow the sequence.

The good news is that JS is getting it soon.

However it is still in proposal stage 1 with the TC39 so might take a while

Further reading

There are so many other areas worth investigation if you are interested. They include:

Modules

  • Functions and scope

Engine

  • V8 single thread event loop vs BEAM Processes
  • Performance
  • Concurrency models

Testing

  • Elixir — “Mock as a noun” philosophy
  • Node — Real Wild West of testing 🤠

Why bother with JS then?

We have been focusing from the perspective of Elixir, but JS has lots of good things about it. Here is a small list of points which remind me why its still important today:

  • Ubiquitous: Only language to run natively on the browser and most IOT’s.
  • Architecture parity: Do you want to run the same code on your FE and BE? Thats very powerful.
  • Tooling : The npm numbers are very impressive (>1M packages, >13.5B weekly downloads)
  • Community: Most questions you can think of have already been answered on StackOverflow or one of a number of forums
  • 3rd party support: Most software opts for JS as its SDK client, so its always easy to get started.
  • Flexible: Sometimes this is also its biggest flaw, but it allows it to be very powerful
  • Simple and easy to learn: The barrier to entry with JS is among the lowest across all languages
  • Growing feature-set: TC39 committee work hard to test and introduce useful new features, really adding value to the language.

I hope you have found this article useful or at least interesting. Please leave a clap if you do. Thanks :)

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Craig Taub
Craig Taub

No responses yet

Write a response