elixir, the hipster language

philip giuliani, daniel morandini, andrea janes

who we are

  • philip giuliani, developer @ keep in mind

  • daniel morandini, developer @ keep in mind

  • andrea janes, researcher @ free university of bozen-bolzano

topics

this is how we will spend our/your time:

conclusion and finish the buffet!

functional vs oop

(introduction to) processes

language highlights

introduction

introduction

erlang

  • ericsson language
  • joe armstrong
  • 31 years ago
  • beam vm

 

goals

  • scalability
  • distribution
  • fault tolerance
  • high availability
  • hot-code swapping 

who uses erlang?

basically everyone already used it in some way

whatsapp

  • messaging servers
  • 2011: 1 million concurrent tcp connections
  • 2012: they reached 2 million
The WhatsApp architecture Facebook bought for 19 billion

* http://highscalability.com/blog/2014/2/26/the-whatsapp-architecture-facebook-bought-for-19-billion.html

uses erlang to implement its chat service, handling more than 2 billion montly active users

uses erlang to implement simpledb, a database service of the amazon elastic compute cloud

uses erlang in its sms and authentication systems

migrated from ruby to elixir and went from 150 servers to just 5

erlang?

isn't this talk about elixir?

elixir=erlang

  • jose valim
  • compiles to erlang
  • ruby-like syntax (jose was part of the rails core team)
  • 1.0 released 2011 after 8005 commits
  • it is functional! (more about this later)

By the way

Ask questions any time!

functional vs oop

a historical perspective*

  • “I guess by end of 70's people were burnt out [...] it made sense that what you can understand better will be manageable better.”
  • “Object-oriented methodology with a promise '...everything in life is an object' seemed more like common sense even before it was proven to be meaningful.”
  • “[...] this paradigm forced you to somewhat think before you code more than procedural languages, it showed visible success on the software projects that employed, and since then they caught on!”

* https://softwareengineering.stackexchange.com/questions/137716/what-were-the-historical-conditions-that-led-to-object-oriented-programming-beco

the biggest differences*

object-oriented programming:

  • a programmer creates objects
  • encapsulation: data and operations are kept together
  • inheritance: classes can be reused
    • more and more criticized because if used intensively, it hinders reuse and agility
    • composition is an alternative
  • polymorphism: term used for a lot of things, interfaces are an alternative
    • function overloading
    • generics
    • subtype polymorphism

* https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53 and https://stackoverflow.com/questions/1112773/what-are-the-core-concepts-in-functional-programming

the biggest differences*

functional programming:

  • a programmer creates functions
  • data and operations are kept separate
  • no inheritance, composition
  • polymorphism
    • “function overloading” (guards)
    • generics
    • “interfaces” (protocols)

* https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53 and https://stackoverflow.com/questions/1112773/what-are-the-core-concepts-in-functional-programming

an intuitive explanation

one thing they sometimes teach you in programming classes is how to study requirements to extract classes and methods.

  1. "The user shall be able to search either all of the initial set of databases or select a subset from it."
  2. "The system shall provide appropriate viewers for the user to read documents in the document store."
  3. "Every order shall be allocated a unique identifier (ORDER_ID) which the user shall be able to copy to the account’s permanent storage area."

is functional/oop programming a hype?*

* https://www.quora.com/Is-the-object-oriented-programming-OOP-paradigm-overemphasized-or-overrated

60ies

90ies

what oop users claim*

* http://i.imgur.com/Q0vFcHd.png

what actually happens*

* http://i.imgur.com/Q0vFcHd.png

what to choose?

  • Object-oriented languages are good when you have a fixed set of operations on things, and as your code evolves, you primarily add new things. This can be accomplished by adding new classes which implement existing methods, and the existing classes are left alone.
  • Functional languages are good when you have a fixed set of things, and as your code evolves, you primarily add new operations on existing things. This can be accomplished by adding new functions which compute with existing data types, and the existing functions are left alone.

[5] https://stackoverflow.com/questions/2078978/functional-programming-vs-object-oriented-programming

language highlights

we picked five aspects to describe elixir:

  • the tooling system
  • pattern matching
  • the pipe |> operator
  • why you don't need “if"
  • why you cannot use “while”

the tooling system

two tools to rule them all...

mix

mix help new

project structure

iex

the interactive shell

pattern matching

a = 1 # 1

1 = a # 1 - also a valid statement

2 = a # MatchError


= does not mean to assign, it is a match operator

{:ok, message} = {:ok, "hello, elixir"}
message # "hello, elixir"

{:status, code} = {:status, 404}
code # 404

{:error, err, _} = {:error, "some error message"} # MatchError - 
# tuples with different sizes

{:ok, :hi} = {:ok, "hi"} # MatchError

tuples

list = [1, 2, "last"]
[head|tail] = list
head # 1
tail # [2, "last"]

lists

can also be used to prepend items to a list

list = [0|list]
list # [0, 1, 2, "last"]
list = [{:a, 1}, {:b, 2}] # - can also be written [a: 1, b: 2]

[a: x, b: y] = list # - same number of tuples to match
x # 1
y # 2

keyword lists

map = %{:name => "jose", :surname => "valim"} # elixir's creator

%{name: "jose", surname: "valim"} == map # true - convinient way to write
# maps when keys are atoms only

%{"name" => x} = map
x # "jose"

maps

a = 1
a # 1
{a, b} = {"foo", 2}
a # "foo" - ...

the pin ^ operator

how to match something without hardcoding?

using ^

a = 1
a # 1
{^a, b} = {"foo", 2} # MatchError - hurray
{^a, b} = {1, 2} # - ^ does not allow variable rebind

the pipe |> operator

|>

foo(bar(baz(new_function(other_function()))))

instead of writing

other_function()
|> new_function()
|> baz()
|> bar()
|> foo()

you can write

(As you do already in Linux if you call a command like ls | less)

if? what?

control flow

the elixir way

pattern matching, pattern matching, pattern matching

control flow (1)

case result do
    {:ok, data} -> # do something with data
    {:error, err} -> # do something with err 
    _ -> # whaat?
end
# match and bind `code`
def handle_response(:ok, %{status: code}) do

# match and bind `message`
def handle_response(:error, message) do

# match everything and bind data with a guard
def handle_response(_, data) when is_map(data) do

# match everything and bind data
def handle_response(_, data) do

parameters

case

control flow (2)

with {:ok, url} <- build_url(),
     {:ok, response} <- fetch_users(url),
     {:ok, users} <- parse_response(response) do
  
  do_something(users)
else
  # error handling
end

with

there are also "if", "unless" and "cond"

looping

functional languages

do not need for loops

recursion

# elixir

def sum([]), do: 0
def sum([h|t]), do: h + sum(t)
// golang

func sum(l []int) int {
	result := 0
	for _, v := range l {
		result += v
	}

	return result
}

example: summing integers in a list

# result

sum([1, 2, 3, 4]) = 10

tail recursion

# recursion

def sum([]), do: 0
def sum([h|t]), do: h + sum(t)
# tail recursion

def sum(l), do: do_sum(l, 0)

defp do_sum([], acc), do: acc
defp do_sum([h|t], acc), do: do_sum(t, acc + h)
odd? = &(rem(&1, 2) != 0)

Enum and Stream

1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum # 7500000000

the std library provides also a lot of functions for handling with enumerables (such as list and map)

Stream - lazy evaluation

Enum - for eager usage

1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum # 7500000000

(intro to) processes

# spawn/1 creates and runs a function into a new process
pid = spawn(f) # #PID<0.145.0>

Process.alive?(pid) # false - f returned already..

spawn

self() # #PID<0.84.0> - what is this?
f = fn -> "hello world, from #{inspect self()}" end

let's start with an anonymous function..

..and spawn it into another process!

here = self() # #PID<0.84.0>

spawn(fn -> f.(here) end) # - f.() executes anonymous functions

# flush/0 flushes and prints all the messages in the mailbox
flush() # hello world, from #PID<0.127.0>

sending messages

# send/2 Sends a message to the given dest and returns the message.
f = fn pid -> send(pid, "hello world, from #{inspect self()}") end

let's modify our function a bit

receiving messages

f = fn pid -> send(pid, {:mess, "hello world, from #{inspect self()}"}) end

let's start again with our function

here = self() # #PID<0.84.0>

spawn(fn -> f.(here) end)
receive do
    # pattern matching
    {:error, e} -> e
    {:mess, m} -> m # hello world, from #PID<0.138.0>
end

block and wait for a message to come!

defmodule Counter do    
    def start(val), do: spawn(__MODULE__, :loop, [val])

    def loop(state) do
        receive do
            {:status, pid} ->
                send(pid, "status -> #{inspect state}")
                loop(state)
            :incr ->
                loop(state + 1)
            {:stop, pid} ->
                send(pid, "stopping. last status -> #{inspect state}")
        end
    end
end

common pattern

iex(3)> pid = Counter.start(0)
iex(4)> send(pid, {:status, self()})
iex(5)> flush
"status -> 0"
iex(6)> send(pid, :incr)
iex(7)> send(pid, {:status, self()})
iex(8)> flush
"status -> 1"
self() # #PID<0.120.0>

pid = spawn(f) # - using spawn/1
# 15:24:04.535 [error] Process #PID<0.108.0> raised an exception
# ** (RuntimeError) ouch
#     (stdlib) erl_eval.erl:668: :erl_eval.do_apply/6
#
# - crashed and exited immediately

Process.alive?(pid) # false - crashed immediately

self() # #PID<0.120.0> - as nothing happend

links (1)

# raise/1 causes a RuntimeError
f = fn -> raise("ouch") end

making a process crash using spawn

self() # #PID<0.120.0>

pid = spawn_link(f)
# ** (EXIT from #PID<0.104.0>) evaluator process exited with reason: 
# an exception was raised:
# ** (RuntimeError) ouch
#     (stdlib) erl_eval.erl:668: :erl_eval.do_apply/6
#
# 15:29:04.053 [error] Process #PID<0.110.0> raised an exception
# ** (RuntimeError) ouch
# (stdlib) erl_eval.erl:668: :erl_eval.do_apply/6

Process.alive?(pid) # false - crashed immediately, as expected

self() # #PID<0.126.0> - we crashed as well!

links (2)

making a process crash using spawn_link

but.. why would you do that?

self() # #PID<0.126.0>
pid = spawn_link(f)
Process.flag(:trap_exit, true)

links (3)

f = fn -> receive do :crash -> raise "ouch" end end

making a process crash using spawn_link and trapping exit signals

send(pid, :crash)
# 15:50:31.804 [error] Process #PID<0.140.0> raised an exception
# ** (RuntimeError) ouch
#     (stdlib) erl_eval.erl:668: :erl_eval.do_apply/6
#
# - process crashed as expected but..

self() # #PID<0.126.0> - .. we did not!

# hang on
flush()
# {:EXIT, #PID<0.140.0>,
# {%RuntimeError{message: "ouch"},
#  [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 668]}]}}
#
# - we received the exit message from the process !!

no boundaries

send(dest, message) # - from the official documentation
...
dest may be a remote or local PID, a (local) port, a locally registered name, 
or a tuple {registered_name, node} for a registered name at another node.

defensive programming

Have you ever programmed something like this?

URL url = null;
try {
	url = new URL(urlAsString);
} catch (MalformedURLException e) {
	logger.error("Malformed URL", e);
}
 
HttpURLConnection connection = null;
try {
	connection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
	logger.error("Could not connect to " + url, e);
}

StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new ...

"let it crash"

“Let it crash” refers to a coding philosophy that “if there are any errors, the process is automatically terminated, and this is reported to any processes that were monitoring the crashed process.”

supervisor

worker

supervisor

worker

worker

... there is much more:

that's it!

philip giuliani, developer @ keep in mind

     
 

daniel morandini, developer @ keep in mind

 

andrea janes, researcher @ free university of bozen-bolzano

philipgiuliani

@PhilipGiuliani

danielmorandini

@MorandiniDaniel

ajanes

Meetup Elixir

By Daniel Morandini

Meetup Elixir

  • 1,676