Blog

Elixir for JavaScript Developers

Elixir is a dynamic, functional programming language for building scalable and maintainable applications.

It’s runs on the BEAM virtual machine used to implement the Erlang programming language. Elixir builds on top of Erlang and shares the same abstractions for building distributed, fault-tolerant applications. It’s means you can use all the packages that is built for Erlang in your Elixir applications.

Let’s understand Elixir language constructs using the JavaScript language constructs.

Credits

https://elixirforajs.dev/ (I am adding more examples for my reference as a post here. All credits to this original website)

Code Conventions

Elixir uses snake_cased naming whereas JavaScript uses camelCasing.

E.g.

JavaScript

// camelCasing
const welcomeMessage = "Welcome to Elixir for JavaScript Devs";
const welcomeStatement = () => {
  return "Welcome to Elixir for JS Devs";
}

Elixir

# snake_cased
welcome_message = "Welcome to Elixir for JS Devs"

def welcome_statement do
 "Welcome to Elixir for JS Devs"
end

Code comments

JavaScript uses the standard ‘C’ style comments. /* Comments goes here */
For single line comments two forward slash can be used as well // Like this

Variable Declarations (or binding)

If you know how to declare variables in JavaScript, well you almost know how it works in Elixir as well.

JavaScript

const foo = 1;
let bar = "bar";
var baz = "baz";

// Export constants for reuse and to avoid duplication
export const URL = "https://teachyourselfcoding.com";

// Symbols
let s = Symbol('foo');

Elixir

foo = 1

# atoms are elixir variables that are used to represent a contant whose 
# value is it's own name\

# Example
:ok
:error

# For exporting the variables or functions to be used by another module declare a module attribute

defmodule MySharedModule do
  @foo 1
end

# Note that module attribute values are computed at compile time 
# so assigning the return value of a function call to a module attribute 
# will remain that value once compiled.

defmodule MySharedModule do
  @foo DateTime.utc_now()
end

Comparison Operator

Things get a bit different here. See the pin ^ operator in the example below.

JavaScript

"hi" = "hi"   // Uncaught SyntaxError
"hi" == "hi"  // true
"hi" === "hi" // true

Elixir

"hi" = "hi"  # "hi"
"hello" = "hi" # (MatchError)
"hi" == "hi"  #true
"hello" == "hi" false

# More example
a = 10   # 10
10 = a   # 10 as both matches
20 = a   # ** (MatchError) no match of right hand side value: 10

# (note if right side is a const and left hand side is a variable then the right hand side is assigned to lhs)
a = 20  # 20

# Use pin operator (^) to pin the variable for comparison
a = 20
^a = 20   # 20
^a = 30   # ** (MatchError) no match of right hand side value: 20

In Elixir “=” is the match operator. If the value on the left hand side is a variable and right hand side a const then the right hand side value is assigned to the left hand side variable.

Imports

JavaScript/ES6

import React, { useState } from 'react';
import React as MyReact from 'react';

Elixir

# In elixir, you can use a fully qualified name without importing,
# or you can import like this:
import MyProject.HelperModule

# or alias HelperModule to avoid typing the fully qualified name when used
alias MyProject.HelperModule

# or if you only want to mixin certain functions into your module from
#   HelperModule:
import MyProject.HelperModule, only: [my_function, 1]

Functions

JavaScript

// Normal function
function add(n1, n2) {
  return n1 + n2;
}

// Arrow function
const add = (n1, n2) => n1 + n2;

Elixir

 # Normal function
 def add(n1, n2) do
  n1 + n2
 end

 # Arrow function
 add = fn(a, b) -> a + b end

 # Using capture syntax
 add = &(&1 + &2)

 # You can invoke the above function as shown below
 add.(1,2)   # 3

 # functions in elixir can be private
 defp private_add(n1, n2) do
  n1 + n2
 end

Method Chaining

JavaScript

const people = [{name: 'Alice', age: 18}, {name: 'Bob', age: 30}];
const filteredAndMapped = people
   .filter({age} => age > 21)
   .map({name} => name);  // Bob

Elixir (|> pipe operator)

people = [%{name: "Bob", age: 30}, %{name: "Alice", age: 18}]
old_names = people
 |> Enum.filter(fn %{age: age} -> age > 21 end)
 |> Enum.map(fn n -> n.name end)

# The above code is same as
old_names = Enum.filter(people, fn %{age: age} -> age > 21 end)
names = Enum.map(old_names, fn n-> n.name end) # ["Bob"]

NOTE: The pipe operator will be available in the upcoming ECMA script edition. The proposal is here and can be currently used with Babel as transpiler as well.

https://github.com/tc39/proposal-pipeline-operator

The pipe operator enables passing implicit parameter from one function to another.

Destructuring

JavaScript

 const o = { nested: { prop: 'Hi!' } };

 const { nested: { prop } = {} } = o;

 console.log(prop);
 // Hi!

Elixir

o = %{nested: %{prop: "Hi!"}}

%{nested: %{prop: prop}} = o

IO.inspect(prop)  # Hi!

Pattern Matching

JavaScript

 const list = ( user ) => {
   if (user.isAdmin) {
     return doSomething();
   }
   return doSomethingElse();
 }

Elixir

# if the user passed in is an admin, this
# function will be called

defp list(%{is_admin: true}) do
 doSomething()
end

# regular users have this function called
defp list(user) do
 doSomethingElse()
end

Control Flow

JavaScript

 const response = fetchApi("https://someurl.com/posts");

 switch (response.status) {
   case 200:
     return "Success";
   case 401:
     return "Not Allowed";
   default:
     return "There was an error";
 }

Elixir

# very common in elixir to use case statements
 # to match on the results of a function. In this
 # example get_a_response returns a tuple where the
 # first element is the status

 case fetchApi("https://someurl.com/posts")do
  {:ok, _} ->
    "Success"

  {:error, %{reason = "Unauthorized"}} ->
    "Not Allowed"

  {:error, _} ->
    "There was an error"
 end

 cond do
  foo == "foo" ->
    "Success"

  bar < 1 ->
    "Not Allowed"

  _ ->   # This is default clause
    "There was an error"
 end

If/Else Statements

JavaScript

if (n > 100) {
   return 100;
 } else {
   return n;
}

n > 100 ? 100 : n;

Elixir

if n > 100 do
  100
else
 n
end

# unless keyword is a sort of reverse if
unless n > 100 do
 n
else
 100
end

 # ternary isn't really directly supported, but this version of
 # an if statement is one line and very easy to read

 if n > 100, do: 100, else: n

Common List Operations

JavaScript

 const a = [1, 2, 3];
 const b = a.map(n => n * 2);
 // b is [2, 4, 6]

Elixir

a = [1, 2, 3]
b = Enum.map(a, fn n -> n * 2 end)
# b is [2, 4, 6]

Filter

JavaScript

const people = [{name: 'Bob', age: 30}, {name: 'Bill', age: 18}];
const oldEnough = people.filter({age} => age > 21);

Elixir

people = [%{name: "Bob", age: 30}, %{name: "Bill", age: 18}]
old_enough = Enum.filter(people, fn %{age: age} -> age > 21 end)

Reduce

JavaScript

const sum = [1, 2, 3].reduce((acc, n) => n + acc, 0)
// sum is 6

Elixir

sum = Enum.reduce([1, 2, 3], 0, fn n, acc -> n + acc end)
# sum is 6

Object

JavaScript

const Square = {
  unit: "sq",
  area: (s) => `${s * s} ${Square.unit} unit `,
};

console.log(Square.area(4)); // 16 sq unit

Elixir

defmodule Square  do
  def unit, do: "sq"
  def area(s), do:
    "#{s * s} #{unit} unit"
end

Square.area(4) # 16 sq unit

Closures

A closure refers to the value of variable or a function in an outer scope being available in the context of the inner scope, even if that inner scope is invoked at a much later and outer scope is no longer available.

JavaScript – Example 1

function fnFactory() {
  let number = 7;
  return () => console.log(number);
}

let someFunction = fnFactory();

someFunction();  // Log 7

Here in the above example even thought the ‘number’ variable has gone out of scope after the invocation of fnFactory(), it’s value was preserved because it was referenced in the inner scope of the function that was returned. That’s one example of closure for you.

JavaScript – Example 2

What happens if you change the value of a variable that was captured in a closure?

let firstName = "Pinku";

let printName = () => console.log(firstName );  

printName();  // Pinku

firstName = "Chinku";

printName();   // Chinku

From the above example it is clear that closure in JavaScript will refer to the captured variable by reference. So, if the value of the captured variable is changed between the time it is captured and the time that it is used, the updated value will be reflected when it is used again.

Elixir

defmodule Example do 
  def fnFactory() do
    number = 7
    fn -> console.log(number) end
  end
end

someFunction = Example.fnFactory();

someFunction.();  // Log 7  (Note the '.' after someFunction)

Elixir- Example 2

What happens if you change the value of a variable that was captured in a closure?

first_name = "Pinku"

print_name = fn -> IO.puts(first_name) end

print_name.()  # Pinku

first_name = "Chinku"

print_name.()   # Pinku  (Observe the change in output here)

The ‘first_name’ variable printed by the closure always has the same value, even though the value of the ‘first_name’ has been changed.

So, we can concluded that in Elixir the ‘value’ of the variable is captured by the closure and not the reference to the variable. Since data in Elixir is immutable, the inner scope continues to refer to the original value and not the modified value, which is located somewhere else in memory.

This if kind of good in the sense that in a functional language side effects are not a good practice unless explicitly needed.

More examples of closures in anonymous function in Elixir.

animal_talk = fn (something) -> (fn -> IO.puts(something) end) end
say_dog = animal_talk .("dog")
say_cat = animal_talk .("cat")

say_dog.()  # dog
say_cat.()  # cat

How useful was this post?

Click on a heart to rate it!

Average rating 4.7 / 5. Vote count: 9

No votes so far! Be the first to rate this post.

Leave a Reply