Elixir for a JS Dev
Expand a section to learn about a particular topic.
Code Conventions
Javascript
// camelCasing
const welcomeStatement = "Welcome to Elixir for JS Devs"
const welcomeStatement = () => {
return "Welcome to Elixir for JS Devs"
}
Elixir
# snake_cased
welcome_statement = "Welcome to Elixir for JS Devs"
def welcome_statement do
"Welcome to Elixir for JS Devs"
end
Variable Declaration (or binding)
Javascript
const foo = 1;
let bar = 2;
var baz = 3;
// Export constants for reuse and to avoid duplication
export const FOO = 1;
Elixir
foo = 1
# atoms are elixir variables that are used to
# represent a constant whose value is its own name
# they are often used to express status
:ok
:error
# Declare module attributes for similar purposes as constant exports in JS
defmodule MySuperSpecialProject 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 MySuperSpecialProject do
@foo DateTime.utc_now()
end
"=" is called the match operator. The match operator is similar to triple equals in Javascript.
Unlike JS, all elixir data is immutable and must be assigned a value at the time the variable is declared.
Comparison Operators
Javascript
"hi" = "hi" # Uncaught SyntaxError: Invalid left-hand side in assignment
"hi" == "hi" # true
"hi" === "hi" # true
Elixir
"hi" = "hi"
"hello" = "hi" # ** (MatchError) no match of right hand side value: "hi"
"hi" == "hi" # true
"hello" == "hi" # false
Imports
Javascript
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 MySuperSpecialProject.HelperModule
# or alias HelperModule to avoid typing the fully qualified name when used
alias MySuperSpecialProject.HelperModule
# or if you only want to mixin certain functions into your module from
# HelperModule:
import MySuperSpecialProject.HelperModule, only: [my_function, 1]
Functions
Javascript
function add(n1, n2) {
return n1 + n2;
}
const add = (n1, n2) => n1 + n2;
Elixir
def add(n1, n2) do
n1 + n2
end
add = fn(a, b) -> a + b end
add = &(&1 + &2)
# functions in elixir can be private
defp private_add(n1, n2) do
n1 + n2
end
The ampersand operator is the capture operator, documented here.
Method Chaining
Javascript
const people = [{name: 'Bob', age: 30}, {name: 'Bill', age: 18}];
const filteredAndMapped = people.filter({age} => age > 21).map({name} => name);
// ['Bob']
Elixir
people = [%{name: "Bob", age: 30}, %{name: "Bill", age: 18}]
old_enough_names = Enum.filter(people, fn %{age: age} -> age > 21 end)
|> Enum.map(n, fn n -> n.name end)
# ["Bob"]
# The pipe operator (|>) can be applied to more than just arrays:
score = 45
score
|> Kernel./(2)
|> :math.pow(-1)
|> Kernel.*(100)
# equivalent to (((45 / 2) ^ -1) * 100)
The pipe operator is a very powerful tool in Elixir - read more about it here.
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 is the Elixir equivalent of destructuring. In Elixir it is common to have multiple functions defined with the same name that match on different properties:
Javascript
const list = ( user ) => {
if (user.isAdmin) {
return store.listAll();
}
return store.listForUser(user);
}
Elixir
# if the user passed in is an admin, this
# function will be called
defp list(%{is_admin: true}) do
store.list_all()
end
# regular users have this function called
defp list(user) do
store.list_for_user(user)
end
There are more examples of pattern matching in action in the Control Flow section of this site.