July 20th 2016
ORGANIZERS
Charles King
Mike Groseclose
Omer Wazir
Chris Steinmeyer
What is elixir?
What is Erlang/OTP?
What problems does it solve?
When should you use it?
Our favorite Parts
Resources for learning Elixir/OTP
“Elixir is a dynamic, functional language designed for building scalable and maintainable applications. Elixir leverages the Erlang VM, known for running low-latency, distributed and fault-tolerant systems, while also being successfully used in web development and the embedded software domain.”
parent = self()
# Spawns an Elixir process (not an operating system one!)
spawn_link(fn ->
send parent, {:msg, "hello world"}
end)
# Block until the message is received
receive do
{:msg, contents} -> IO.puts contents
end
# simple list
[iex(1)> organizers = [:charlie,:chris,:omer,:mike]
[:charlie,:chris,:omer,:mike]
# delete an item
[iex(2)> List.delete(organizers, :charlie)
[:chris, :omer, :mike]
$ mix new my_app
$ cd my_app
$ mix test
.
Finished in 0.04 seconds (0.04s on load, 0.00s on tests)
1 tests, 0 failures
$ iex
Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)
iex> c "my_file.ex" # Compiles a file
iex> t Enum # Prints types defined in the module Enum
iex> h IEx.pry # Prints the documentation for IEx pry functionality
iex> i "Hello, World" # Prints information about the given data type
# Quoting
iex> quote do: sum(1, 2, 3)
# Elixir AST
{:sum, [], [1, 2, 3]}
# Unquoting
iex> number = 13
iex> Macro.to_string(quote do: 11 + number)
"11 + number"
iex> number = 13
iex> Macro.to_string(quote do: 11 + unquote(number))
"11 + 13"
Any sufficiently complicated concurrent program in another language contains an ad hoc informally-specified bug-ridden slow implementation of half of Erlang"
Rober Virding
Erlang was built in 1986 to solve Ericsson's software problems
Erlang is a general-purpose programming language and runtime environment. Erlang has built-in support for concurrency, distribution and fault tolerance
Erlang runs on a VM called BEAM
Set of middleware, tools and libraries to help you write resilient code
Fast for Computers
Fast for Humans
Python
Ruby
JavaScript
C
C++
Elixir
Go
Rust
Java
Erlang
“Elixir is optimized for developer happiness” – via @mwoods79
Created by active Ruby contributor Jose Valim
Having trouble scaling your Ruby application
To easily use more than one CPU core (shame on NodeJS)
Want to embrace functional programming, with friendly semantics
Charlie - Processes
Mike - Pattern Matching
Chris - Umbrella Projects
Omer - Streams & Flows
"Processes are isolated from each other, run concurrent to one another and communicate via message passing"
"...not be confused with operating system processes. Processes in Elixir are extremely lightweight in terms of memory and CPU... ...not uncommon to have tens or even hundreds of thousands of processes running simultaneously."
iex> pid = spawn fn -> 1 + 2 end
#PID<0.44.0>
iex> Process.alive?(pid)
false
Processes - Contrived Example
Real world example
Elixir Supervisor
Elixir Processes running NodeJS
http request
html
Streams are composable, lazy enumerables. Any enumerable that generates items one by one during enumeration is called a stream. A stream will not consume or output an item until a regular Enum asks.
In Elixir the Enum module is eager, always consuming an entire collection and often resulting in another collection. Piped enumerable operations create intermediary collections.
iex(1)> organizers = ["Charlie","Chris","Mike","Omer"]
iex(2)> Enum.with_index(organizers)
[{"Charlie", 0}, {"Chris", 1}, {"Mike", 2}, {"Omer", 3}]
iex(3)> organizers |>
...(3)> Enum.with |>
...(3)> Enum.each( fn(organizer, index) ->
...(3)> IO.puts "#{index + 1}. #{organizer}"
...(3)> end)
1. Charlie
2. Chris
3. Mike
4. Omer
Using the Enum module creates intermediary collections
iex(1)> organizers = ["Charlie","Chris","Mike","Omer"]
iex(2)> Stream.with_index(organizers)
#Stream<[enum: ["Charlie", "Chris", "Mike", "Omer"],
funs: [#Function<62.89908360/1 in Stream.with_index/2>]]>
iex(3)> organizers |>
...(3)> Stream.with |>
...(3)> Enum.each( fn(organizer, index) ->
...(3)> IO.puts "#{index + 1}. #{organizer}"
...(3)> end)
1. Charlie
2. Chris
3. Mike
4. Omer
def getWords(path) do
File.stream!(path)
|> Stream.flat_map(&String.split(&1, " "))
|> Enum.reduce(%{}, fn word, acc ->
Map.update(acc, word, 1,& &1 + 1)
end)
|> Enum.to_list()
end
Using Stream.with_index creates a recipe of computation - nothing is evaluated until Enum.each is called
GenStage.Flow executes computation stages in parallel
[file stream] # Flow.from_enumerable/1 (producer)
| |
[M1] [M2] # Flow.flat_map/2 (producer-consumer)
|\ /|
| \/ |
|/ \ |
[R1] [R2] # Flow.reduce/3 (consumer)
M1 & M2 stages receive lines and break them into words
M1 - ["roses", "are", "red"]
M2 - ["violets", "are", "blue"]
Words are consistently routed to R1 or R2 regardless of origin
R1 - ["roses", "are", "red", "are"]
R2 - ["violets", "blue"]
Results of reduced stages
R1 - %{"roses" => 1, "are" => 2, "red" => 1}
R2 - %{"violets" => 1, "blue" => 1}
def getWords(path) do
File.stream!(path)
|> Enum.flat_map(&String.split(&1, " "))
|> Enum.reduce(%{}, fn word, acc ->
Map.update(acc, word, 1,& &1 + 1)
end)
|> Enum.to_list()
end
def getWords(path) do
File.stream!(path)
|> Stream.flat_map(&String.split(&1, " "))
|> Enum.reduce(%{}, fn word, acc ->
Map.update(acc, word, 1,& &1 + 1)
end)
|> Enum.to_list()
end
alias Experimental.GenStage.Flow
def getWords(path) do
File.stream!(path)
|> Flow.from_enumerable()
|> Flow.flat_map(&String.split(&1, " "))
|> Flow.partition()
|> Flow.reduce(fn -> %{} end, fn word, acc ->
Map.update(acc, word, 1, & &1+ 1)
end)
|> Enum.to_list()
end
Word count performance in microseconds reading a 12 megabyte text file
1 = x because the left hand matches the right hand.
What happens if we try 2 = x ?
The left hand side does not match the right hand side, so an error is thrown.
Left hand side can contain values 😱
As we'd expect, a match error will occur when a match can't be made.
Structs are extensions built on top of maps that provide compile-time checks and default values.
Let's match against a user struct based on the User.count matching 1
case allows us to compare a value against many patterns until we find a matching one
Let's assume we have soft-deletable Users.
Let's find a User (if they are not deleted).
The wrong way.
Better.
👍
A project that contains multiple applications.
Programming Elixir 1.2 by Dave Thomas
Elixir in Action by Saša Jurić
Introducing Elixir by Simon St. Laurent, J. David Eisenberg
The Little Elixir and OTP Guidebook by Benjamin Tan Wei Hao
Erlang books (Great for learning OTP too)
Learn You Some Erlang for great good! by Frederic Trottier-Hebert
Introducing Erlang by Simon St. Laurent
Designing for Scalability with Erlang/OTP by Francesco Cesarini, Steve Vinoski
Exercism.io
Codewars.com
reddit.com/r/dailyprogrammer
Études for Elixir
cryptopals crypto challenges
https://elixirforum.com
#myelixirstatus on Twitter
Elixir Fountain podcast
Elixir on Slack - https://elixir-slackin.herokuapp.com/.
Elixir source code on Github
Erlang Solutions YouTube channel
Riak and Erlang/OTP - The Architecture of Open Source Applications http://www.aosabook.org/en/riak.html
For large OTP apps see RabbitMQ or Riak