elixir, the hipster language

andrea janes, free university of bozen-bolzano
philip giuliani, daniel morandini, kim
topics
this is how we will spend our/your time:
conclusion
(introduction to) processes
language highlights
introduction
introduction
elixir=erlang

- jose valim
- compiles to erlang
- ruby-like syntax (jose was part of the rails core team)
- useful for parallel/distributed challenges
- 1.0 released 2011 after 8005 commits
- it is functional!
the biggest differences*
object-oriented programming:
- a programmer creates objects
- encapsulation: data and operations are kept together
- inheritance: classes can be reused
-
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
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 four aspects to describe elixir:
- pattern matching
- the pipe |> operator
- why you don't need “if"
- why you cannot use “while”
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, description} = {: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
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?
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


philipgiuliani
@PhilipGiuliani


danielmorandini
@MorandiniDaniel

ajanes
andrea janes, researcher @ free university of bozen-bolzano
ajanes@unibz.it
SFSCon: Elixir, the hipster language
By Andrea Janes