introducing elixir and otp at the erlang bash

68
Elixir and OTP Chris McGrath @chrismcg [email protected]

Upload: devbash

Post on 17-Jul-2015

269 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Introducing Elixir and OTP at the Erlang BASH

Elixir and OTPChris McGrath

@[email protected]

Page 2: Introducing Elixir and OTP at the Erlang BASH

What is Elixir?

Page 3: Introducing Elixir and OTP at the Erlang BASH

ElixirElixir 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.— http://www.elixir-lang.org

Page 4: Introducing Elixir and OTP at the Erlang BASH

What Elixir is NOT• Some sort of CoffeeScript for Erlang

• A port of Ruby to Erlang

• Just Erlang with nicer syntax

Page 5: Introducing Elixir and OTP at the Erlang BASH

Some example codedefmodule RedirectCounter.URL do @max_redirects 10

def count_redirects(url) do { :ok, response } = HTTPoison.head(url) do_count(response.status_code, response.headers["Location"], 0) end

defp do_count(_status_code, _url, @max_redirects), do: raise "To many redirects"

defp do_count(status_code, url, redirect_count) when status_code in [301, 302, 307] do { :ok, response } = HTTPoison.head(url) do_count(response.status_code, response.headers["Location"], redirect_count + 1) end

defp do_count(_status_code, _url, redirect_count), do: redirect_countend

Page 6: Introducing Elixir and OTP at the Erlang BASH

Why learn Elixir?

Page 7: Introducing Elixir and OTP at the Erlang BASH

Personal Reasons

Page 8: Introducing Elixir and OTP at the Erlang BASH

"Boss" Reasons• Our systems are becoming more and more parallel and the

primitives provided by most languages are quite low level

• Runs on top of the Erlang runtime, famous for amazing uptimes and fault tolerance

• Powerful macro system for creating DSLs and reducing boilerplate

• OTP library and architecture makes it easier to create fault tolerant systems

Page 9: Introducing Elixir and OTP at the Erlang BASH

What's Elixir useful for?• Network related tasks (from plain sockets to web servers

and frameworks)

• Writing reliable, distributed, and highly available software

• MMO backends (not frontends!)

• Using all the cores

• (AKA Things Erlang Is Good For)

Page 10: Introducing Elixir and OTP at the Erlang BASH

What Elixir adds to Erlang• Modules for namespacing

• Macros

• A focus on tooling

• Streaming

Page 11: Introducing Elixir and OTP at the Erlang BASH

What Elixir adds to Erlang• Much nicer string handling

• Consistent function parameters

• Clearer organization of standard library

• Variable rebinding

• Less fiddly syntax

Page 12: Introducing Elixir and OTP at the Erlang BASH

Language Highlights• Mix project management tool

• First class documentation and doctests

• Toll free calling of Erlang functions

• Macros

• Pipeline Operator

• Protocols

Page 13: Introducing Elixir and OTP at the Erlang BASH

Mix• Generates and manages projects

• Somewhat similar to leiningen

• Like Make/Rake it can compile and runs tests

• Like Bundler it allows dependencies to be specified

• Like Rails or Bundler it can generate new project skeletons

• Full integration with Erlang, Rebar, and hex.pm

Page 14: Introducing Elixir and OTP at the Erlang BASH

IEx - Interactive Elixir REPL% iexiex(1)> x = 1 + 23iex(2)> x = 44iex(3)> IO.puts "Hello World"Hello World:ok

Page 15: Introducing Elixir and OTP at the Erlang BASH

Documentation & Doctestsdefmodule ShowDoctest do @moduledoc """ This module shows off an example of a doctest """

@doc """ Adds it's inputs together

iex> ShowDoctest.add(1, 1) 2 """ def add(a, b) do a - b endend

Page 16: Introducing Elixir and OTP at the Erlang BASH

defmodule ShowDoctestTest do use ExUnit.Case, async: true doctest ShowDoctestend

% mix test

1) test doc at ShowDoctest.add/2 (1) (ShowDoctestTest) test/show_doctest_test.exs:3 Doctest failed code: ShowDoctest.add(1, 1) === 2 lhs: 0 stacktrace: lib/show_doctest.ex:12: ShowDoctest (module)

Page 17: Introducing Elixir and OTP at the Erlang BASH

IEx Doc integration% iex -S mixiex(1)> h ShowDoctest

ShowDoctest

This module shows off an example of a doctest

iex(2)> h ShowDoctest.add

def add(a, b)

Adds it's inputs together

Page 18: Introducing Elixir and OTP at the Erlang BASH

Toll free calling into erlangYou can use any available Erlang library in your Elixir project

% erlEshell V6.3 (abort with ^G)1> os:timestamp().{1422,119363,162867}

% iexiex(1)> :os.timestamp{1422, 119376, 391592}

Page 19: Introducing Elixir and OTP at the Erlang BASH

MacrosLisps traditionally empowered developers because you can eliminate anything that's tedious through macros, and that power is really what people keep going back for— Rich Hickey

Page 20: Introducing Elixir and OTP at the Erlang BASH

Macro Exampletest "some sums" do assert 1 + 1 == 3end

1) test some math (TestProjectTest) ** (ExUnit.ExpectationError) expected: 2 to be equal to (==): 3 at test/test_project_test.exs:5

Page 21: Introducing Elixir and OTP at the Erlang BASH

Macro Exampleiex(1)> quote do: 1 + 1 == 3{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 1]}, 3]}

defmacro assert({ :==, _, [l, r]}) do # ...end

defmacro assert({ :=~, _, [l, r]}) do # ...end

Page 22: Introducing Elixir and OTP at the Erlang BASH

Pipelinespeople = DB.find_customersorders = Orders.for_customers(people)tax = sales_tax(orders, 2013)filing = prepare_filing(tax)

Page 23: Introducing Elixir and OTP at the Erlang BASH

Pipelinesfiling = DB.find_customers |> Orders.for_customers |> sales_tax(2013) |> prepare_filing

Page 24: Introducing Elixir and OTP at the Erlang BASH

Pipelines# rewritten to...filing = prepare_filing( sales_tax( Orders.for_customers(DB.find_customers), 2013 ))

Page 25: Introducing Elixir and OTP at the Erlang BASH

Protocols• Let you have polymorphism in Elixir

• Inspired heavily by Clojure

• Can define implementation of built in protocols for your own types

Page 26: Introducing Elixir and OTP at the Erlang BASH

Protocols: Definition1

defprotocol Blank do @doc "Returns true if data is considered blank/empty" def blank?(data)end

1 Sorry, the syntax highlighter doesn't know about protocols yet

Page 27: Introducing Elixir and OTP at the Erlang BASH

Protocols: Implementation1

# Integers are never blankdefimpl Blank, for: Integer do def blank?(_), do: falseend

# Just empty list is blankdefimpl Blank, for: List do def blank?([]), do: true def blank?(_), do: falseend

#...

1 Sorry, the syntax highlighter doesn't know about protocols yet

Page 28: Introducing Elixir and OTP at the Erlang BASH

Protocols: Callingiex> Blank.blank?(0)falseiex> Blank.blank?([])trueiex> Blank.blank?([1, 2, 3])false

Page 29: Introducing Elixir and OTP at the Erlang BASH

Enumerableiex(1)> Enum.map([1, 2, 3], fn(x) -> x * x end)[1, 4, 9]iex(2)> Enum.map([1, 2, 3], &(&1 * &1))[1, 4, 9]

Page 30: Introducing Elixir and OTP at the Erlang BASH

Enumerableiex(1)> stream = Stream.map([1, 2, 3], &(&1 * &1))#Stream<[enum: [1, 2, 3], funs: [#Function<45.29647706/1 in Stream.map/2>]]>iex(2)> stream = Stream.map(stream, &Integer.to_string/1)#Stream<[enum: [1, 2, 3], funs: [#Function<45.29647706/1 in Stream.map/2>, #Function<45.29647706/1 in Stream.map/2>]]>iex(3)> Enum.to_list(stream)["1", "4", "9"]

Page 31: Introducing Elixir and OTP at the Erlang BASH

# More example codedefmodule RedirectCounter.Twitter do def configure do # ... boring setup ... end

def links do configure ExTwitter.stream_filter(track: "link") |> Stream.reject(fn(t) -> t.entities["urls"] == [] end) |> Stream.flat_map(fn(t) -> Enum.map(t.entities["urls"], fn(u) -> u["expanded_url"] end) end) endend

Page 32: Introducing Elixir and OTP at the Erlang BASH

OTP

Page 33: Introducing Elixir and OTP at the Erlang BASH

OTP2

• Large collection of libraries covering a wide range of use cases

• Set of design principles encoded in behaviours

2 Open Telephony Platform - A marketing idea gone bad

Page 34: Introducing Elixir and OTP at the Erlang BASH

Behaviours• Specify callbacks that you implement to specialize your own

code

• Formalize common patterns

• Can create your own

• Four standard ones in Erlang

Page 35: Introducing Elixir and OTP at the Erlang BASH

OTP GenServer

Page 36: Introducing Elixir and OTP at the Erlang BASH

defmodule RedirectCounter.TwitterLinkStream do use GenServer

def start_link do GenServer.start_link __MODULE__, [], name: __MODULE__ end

def init(_) do GenServer.cast __MODULE__, :stream { :ok, nil } end

def handle_cast(:stream, state) do spawn_link fn -> RedirectCounter.Twitter.links |> Enum.each(&RedirectCounter.CounterSupervisor.process/1) end { :noreply, state } endend

Page 37: Introducing Elixir and OTP at the Erlang BASH

defmodule RedirectCounter.Count do use GenServer

def start_link do GenServer.start_link __MODULE__, [], name: __MODULE__ end

def log(redirect_count) do GenServer.cast __MODULE__, { :redirect_count, redirect_count } end

def get do GenServer.call __MODULE__, :get end

def init(_) do { :ok, %{} } end

def handle_cast({:redirect_count, redirect_count}, state) do state = Map.update(state, redirect_count, 1, fn(n) -> n + 1 end) { :noreply, state } end

def handle_call(:get, _from, state) do { :reply, state, state } endend

Page 38: Introducing Elixir and OTP at the Erlang BASH

def start_link do GenServer.start_link __MODULE__, [], name: __MODULE__end

def log(redirect_count) do GenServer.cast __MODULE__, { :redirect_count, redirect_count }end

def get do GenServer.call __MODULE__, :getend

Page 39: Introducing Elixir and OTP at the Erlang BASH

def init(_) do { :ok, %{} }end

def handle_cast({:redirect_count, redirect_count}, state) do state = Map.update(state, redirect_count, 1, fn(n) -> n + 1 end) { :noreply, state }end

def handle_call(:get, _from, state) do { :reply, state, state }end

Page 40: Introducing Elixir and OTP at the Erlang BASH

iex(1)> alias RedirectCounter.Countniliex(2)> Count.start_link{:ok, #PID<0.91.0>}iex(3)> Count.log(1):okiex(4)> Count.log(1):okiex(5)> Count.log(1):okiex(6)> Count.log(2):okiex(7)> Count.log(3):okiex(8)> Count.get%{1 => 3, 2 => 1, 3 => 1}

Page 41: Introducing Elixir and OTP at the Erlang BASH

Call vs Cast

Page 42: Introducing Elixir and OTP at the Erlang BASH

Cast• Asynchronous

• fire & forget

• More decoupled

• Less control over when things happen

Page 43: Introducing Elixir and OTP at the Erlang BASH

Call• Synchronous

• More coupled

• More control over order of events

Page 44: Introducing Elixir and OTP at the Erlang BASH

OTP Supervisors

Page 45: Introducing Elixir and OTP at the Erlang BASH

Supervisors• Don't do any processing

• Start and restart workers and other supervisors

• Prevent errors taking the entire application down

• Shutdown system in a controlled manor

Page 46: Introducing Elixir and OTP at the Erlang BASH

Supervision Trees

Page 47: Introducing Elixir and OTP at the Erlang BASH

defmodule RedirectCounter.Supervisor do use Supervisor

def start_link do Supervisor.start_link(__MODULE__, []) end

def init(_) do children = [ worker(RedirectCounter.Count, []), worker(RedirectCounter.ConsoleOutput, []), supervisor(RedirectCounter.CounterSupervisor, []), worker(RedirectCounter.TwitterLinkStream, []) ]

supervise(children, strategy: :one_for_one) endend

Page 48: Introducing Elixir and OTP at the Erlang BASH

defmodule RedirectCounter.CounterSupervisor do use Supervisor

def start_link do Supervisor.start_link __MODULE__, [], name: __MODULE__ end

def process(url) do {:ok, pid} = Supervisor.start_child(__MODULE__, [url]) GenServer.cast(pid, :count) end

def init(_) do children = [ worker(RedirectCounter.URLRedirectCounter, [], restart: :temporary, shutdown: :brutal_kill) ] supervise(children, strategy: :simple_one_for_one) endend

Page 49: Introducing Elixir and OTP at the Erlang BASH

defmodule RedirectCounter.URLRedirectCounter do use GenServer

def start_link(url) do GenServer.start_link(__MODULE__, url) end

def init(url) do { :ok, url } end

def handle_cast(:count, url) do redirect_count = RedirectCounter.URL.count_redirects(url) RedirectCounter.Count.log(redirect_count) { :stop, :normal, url } endend

Page 50: Introducing Elixir and OTP at the Erlang BASH

Supervision Strategies• one_for_one

• simple_one_for_one

• rest_for_one

• one_for_all

Page 51: Introducing Elixir and OTP at the Erlang BASH

Restart options• permanent

• temporary

• transient

Page 52: Introducing Elixir and OTP at the Erlang BASH

Error Kernel Good Erlang design begins with identifying the error kernel of the system: What part must not fail or it will bring down the whole system?— Jesper Louis Anderson

Page 53: Introducing Elixir and OTP at the Erlang BASH

Error KernelWhenever the kernel is about to do an operation which is dangerous and might crash, you "outsource" that computation to another process, a dumb slave worker. If he crashes and is killed, nothing really bad has happened - since the kernel keeps going.— Jesper Louis Anderson

Page 54: Introducing Elixir and OTP at the Erlang BASH

Agent

Page 55: Introducing Elixir and OTP at the Erlang BASH

# Plain GenServerdefmodule RedirectCounter.Count do use GenServer

def start_link do GenServer.start_link __MODULE__, [], name: __MODULE__ end

def log(redirect_count) do GenServer.cast __MODULE__, { :redirect_count, redirect_count } end

def get do GenServer.call __MODULE__, :get end

def init(_) do { :ok, %{} } end

def handle_cast({:redirect_count, redirect_count}, state) do state = Map.update(state, redirect_count, 1, fn(n) -> n + 1 end) { :noreply, state } end

def handle_call(:get, _from, state) do { :reply, state, state } endend

Page 56: Introducing Elixir and OTP at the Erlang BASH

# Elixir Agentdefmodule RedirectCounter.Count do def start_link do Agent.start_link(fn -> %{} end, name: __MODULE__) end

def log(redirect_count) do Agent.update(__MODULE__, &Map.update(&1, redirect_count, 1, fn(n) -> n + 1 end)) end

def get do Agent.get(__MODULE__, fn(map) -> map end) endend

Page 57: Introducing Elixir and OTP at the Erlang BASH

Task and Task.Supervisor

Page 58: Introducing Elixir and OTP at the Erlang BASH

Simple Exampletask = Task.async(fn -> do_some_work() end)res = do_some_other_work()res + Task.await(task)

Page 59: Introducing Elixir and OTP at the Erlang BASH

# Main Supervisor - Beforedefmodule RedirectCounter.Supervisor do use Supervisor

def start_link do Supervisor.start_link(__MODULE__, []) end

def init(_) do children = [ worker(RedirectCounter.Count, []), worker(RedirectCounter.ConsoleOutput, []), supervisor(RedirectCounter.CounterSupervisor, []), worker(RedirectCounter.TwitterLinkStream, []) ]

supervise(children, strategy: :one_for_one) endend

Page 60: Introducing Elixir and OTP at the Erlang BASH

# Main Supervisor - Afterdefmodule RedirectCounter.Supervisor do use Supervisor

def start_link do Supervisor.start_link(__MODULE__, []) end

def init(_) do children = [ worker(RedirectCounter.Count, []), worker(RedirectCounter.ConsoleOutput, []), supervisor(Task.Supervisor, [[name: :counter_supervisor]]), worker(Task, [RedirectCounter.Twitter, :process, [&RedirectCounter.URL.process/1]]) ]

supervise(children, strategy: :one_for_one) endend

Page 61: Introducing Elixir and OTP at the Erlang BASH

# Previous RedirectCounter.Twitterdefmodule RedirectCounter.Twitter do def configure do # ... boring setup ... end

def links do configure ExTwitter.stream_filter(track: "link") |> Stream.reject(fn(t) -> t.entities["urls"] == [] end) |> Stream.flat_map(fn(t) -> Enum.map(t.entities["urls"], fn(u) -> u["expanded_url"] end) end) endend

Page 62: Introducing Elixir and OTP at the Erlang BASH

# Updated RedirectCounter.Twitterdefmodule RedirectCounter.Twitter do def process(fun) do links |> Enum.each(fun) end

# ...end

Page 63: Introducing Elixir and OTP at the Erlang BASH

# Previous RedirectCounter.URLdefmodule RedirectCounter.URL do @max_redirects 10

def count_redirects(url) do { :ok, response } = HTTPoison.head(url) do_count(response.status_code, response.headers["Location"], 0) end

defp do_count(_status_code, _url, @max_redirects), do: raise "To many redirects" defp do_count(status_code, url, redirect_count) when status_code in [301, 302, 307] do { :ok, response } = HTTPoison.head(url) do_count(response.status_code, response.headers["Location"], redirect_count + 1) end defp do_count(_status_code, _url, redirect_count) do redirect_count endend

Page 64: Introducing Elixir and OTP at the Erlang BASH

# Updated RedirectCounter.URLdefmodule RedirectCounter.URL do def process(url) do Task.Supervisor.start_child(:counter_supervisor, __MODULE__, :count_redirects, [url]) end

def count_redirects(url) do { :ok, response } = HTTPoison.head(url) redirect_count = do_count(response.status_code, response.headers["Location"], 0) RedirectCounter.Count.log(redirect_count) end # ...end

Page 65: Introducing Elixir and OTP at the Erlang BASH

What I haven't covered• gen_event and gen_fsm

• Applications (in Erlang terminology)

• Upgrades and hot code reloading

• Debugging, monitoring, and logging

• The other parts of OTP (ssh, asn.1, ...)

• ets / mnesia (built in "NoSQL" databases)

Page 66: Introducing Elixir and OTP at the Erlang BASH

Interesting Elixir projects• Plug: Rack/WSGI like layer for Elixir

• Phoenix: Batteries included web/websockets framework

• Ewebmachine: Generates HTTP responses based on HTTP decision tree

• Ecto: LINQ inspired database abstraction layer

Page 67: Introducing Elixir and OTP at the Erlang BASH

ElixirConf.eu23rd - 24th April 2015

Krakow, Polandhttp://www.elixirconf.eu

Page 68: Introducing Elixir and OTP at the Erlang BASH

Thanks!I hope I've interested you in Elixir and Erlang/OTP

• http://elixir-lang.org

• Progamming Elixir - Pragmatic Programmers

• Elixir in Action - Manning

• Erlang and OTP in Action - Manning

• http://www.erlang-in-anger.com/