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.