Elixir and Javascript syntax comparison
Maartz
Posted on September 24, 2020
On a daily basis at work I use 2 languages. Java and Javascript.
Doing Javascript everyday and learning Elixir, I recognize some patterns. Let's recap.
Nobody ignores in the web dev planet the features that ES6 ships to the Javascript language, especially the functional ones.
Summary
- Objects vs Modules
- Method chaining vs Pipe Operator
- Destructuring vs Pattern Matching
- High Order Functions
Objects vs Modules
With ES6, the class
keyword and all the OO ceremony come in Javascript
.
In Elixir
, as a functional language, it doesn't support the idea of Object
, instead, we've Modules
, which can be seen as bucket or namespace for functions.
Example of an Object
in Javascript
:
const Circle = {
PI: Math.PI, // Math.PI is a constant
area: r => Circle.PI * (r ** 2),
circumference: r => Circle.PI * (r * 2)
};
Circle.area(2) // 12.56...
Same in Elixir
:
defmodule Circle do
@pi :math.pi() # Here we define a module constant
def area(r), do: @pi * (r * r)
def circumference(r), do: 2 * @pi * r
end
Circle.circumference(5) # 31.41..
So in my opinion, I've gained some good habits from FP, like writing little functions, which are responsible of a tiny modification of the input new output based on the one passed, and the modification asked. So that's what we call a reducer.
And this, let us build very complex data transitioning with ease, so it leads us naturally to the next step: Method Chaining vs Pipe Operator.
A bit of background: Erlang and Elixir
In a precedent example, I've used :math.pi()
which is not an Elixir module, here's why.
Unlike Javascript
, Elixir
does not have a Math module, instead, it leverage the Erlang
standard library. In fact, Elixir
is built on top of Erlang
. Furthermore, Elixir
and Erlang
are interoperable. Which means we can use the huge ecosystem of Erlang
libraries in our Elixir
code. That's pretty neat.
So to call an Erlang
module in Elixir
, we just have to type the following:
:erlang_module.erlang_function()
:math.pi()
:crypto.hash(:md5, data) # To use crypto library and hash with MD5
Method Chaining vs Pipe Operator
So here we are going to take a real-world example, squaring a list of numbers and reducing the value to a single one. So we can use in both languages the map and reduce functions.
Javascript
const numbers = [1,2,3,4,5]\
let sumOfSquares = list
.map(num => num * num)
.reduce((num, acc) => acc + num)
In Elixir
, we will use the pipe operator
list_of_numbers = [1,2,3,4,5]
sum_of_squares =
list_of_numbers
|> Enum.map(&(&1 * &1))
|> Enum.reduce(&(&1 + &2))
Two things here which are Elixir
specific, first the |>
aka pipe operator and second, this exotic piece of syntax '&(&1)
'.
So the pipe operator let us pass the data from a function call to another in a Unix shell fashion. But, as uncle Ben told us, with great power comes great responsibility, well ... kidding, here there's just one rule, your first function parameter should be the output of the previous function. That's all. So map
in both Javascript
and Elixir
returns an Array
or a List
(same thing but different naming).
To truly leverage this pipe operator, you must think in function composition. Here's an example for a simple scrapper that I wrote.
I needed to perform a call to a specific URL, handle the 301 HTTP status, get the correct URL and then make a new call to the properly formatted URL.
def get_url(ean) do
HTTPoison.start()
url =
"#{@url}-#{ean}" # Module constant here
|> HTTPoison.get!() # Call to another Module function
|> get_html() # Module function
|> get_title() # Module function
|> List.to_string() # List Module function call
|> split() # Module function
end
So this pipe operator avoid one thing, function call hell like this:
function3(function2(function1(data))) // works but we loose readability
The pipe operator put the data where it should be. At the top of the attention, after all, that's what we are processing.
As far as I recall, it seems that the pipe operator is in a proposal stage at the TC39. Anyway, it's also available in ReasonML, so in React-Reason.
And what if I told that we can extract data of a variable easily by just describing what we want ?
Destructuring vs Pattern Matching
In Elixir
, we say x = 1
, you probably think that x
is equal to 1
. But there's a subtle difference, we do not say this is equality, we say its a match. The value behind x
is 1
because we have bind the free variable named x
to match the value of 1
. So the =
sign is called the match operator
and not equal
.
Thinks of this as a comparison between the Rhs
and the Lhs
. See how we can leverage this.
Taking MDN docs, destructuring is:
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
So:
let a, b, rest;
[a, b, ...rest] = [10, 20, 30, 40, 50]; // ... or spread operator
a // 10
b // 20
rest // [30, 40, 50]
In Elixir
:
list = [1,2,3,4,5]
a, b = list
** (SyntaxError) iex:2: syntax error before: ','
Oh, it seems that it doesn't work like in Javascript
... So here, list
is a List
type. And in Elixir
, a list is a made of a head and a tail like this [head | tail]
( the pipe here is called the cons operator).
Thus, we can write a list like this [1 | [ 2 | [ 3 ]]]
.
Lets do this in the Elixir
fashion.
list = [1,2,3,4,5]
[a|b] = list
a # 1
b # [2,3,4,5]
# One more time
[a, b, c|d] = list
a # 1
b # 2
c # 3
d # [4,5]
In Javascript
, destructuring is really great, especially in function parameters.
Like in this React component, instead of calling props.title
, props.imageUrl
, etc.
I choose to destructure the props parameter and cherry-pick the values I want to get from.
render() {
return (
<div className="directory-menu">
{
this.state.sections.map(({title, imageUrl, id, size}) => (
<MenuItem key={id} title={title} imageUrl={imageUrl} size={size} />
))
}
</div>
);
}
As I did in this Elixir
snippet:
def draw_image(
%Identicon.Image{color: color, pixel_map: pixel_map})
do
image = :egd.create(250, 250)
fill = :egd.color(color)
Enum.each pixel_map, fn({start, stop}) ->
:egd.filledRectangle(image, start, stop, fill)
end
:egd.render(image)
end
To extract the values from the Identicon.Image
struct, I pattern matched on the fields of the passed struct in the function parameter. But what if we can call functions in function parameters?
High Order Function
In Elixir
, function are first-class citizens, so a function can take a function as a parameter and/or return a function.
So let's get a non-trivial example!
// In ES6 style
const multiplyAll = array => times => array.map(
item => item * times
);
// In ES5 style
var multiplyAll = function multiplyAll(array) {
return function (times) {
return array.map(function (item) {
return item * times;
});
};
};
multiplyAll([2,7,3,60])(2) // [4, 14, 6, 120]
This what we call 🍛 🙌 CURRYFICATION 🍛 🙌!
We can see in ES5 style that we have a function, which returns a function, which uses a method with a lambda AKA anonymous...function!
Yo Dawg, I've heard you like functions so we put functions in your functions which returns functions in the function body... 🤔
In fact it helps a lot. See, we can leverage this style to prevent side effects, and aims pure functions.
In Elixir
, we can do this, this way:
# Here we declare a lambda called run_query
run_query =
fn query_def ->
Process.sleep(2000) ①
"#{query_def} result"
end
# Here another lambda with the previous one inside.
async_query =
fn query_def ->
spawn(fn -> IO.puts(run_query.(query_def)) end)
end
# And finally, we use this lambda in another function call
Enum.each(1..5, &async_query.("query #{&1}"))
# Naively we can achieve this this way
add = fn x ->
fn y -> x + y end
end
add.(1).(3) # 4
This ends the Javascript vs Elixir article, the main goal is not to make a real comparison between the languages but much more leverage the strength of one language to write better code.
As I said earlier, since I've begun my Elixir journey, I realize how I can leverage Elixir's idioms and philosophy to write better Javascript, and vice-versa.
Kudos to you if you have reach the end of this.
Posted on September 24, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.