Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Erlang & Elixir: a gentle welco...

Introduction to Erlang & Elixir: a gentle welcome to the BEAM community

This is a short crash-course onto the main two BEAM languages: Erlang and Elixir. From basic syntax and project structure to processes and automated property-based testing, you'll get a glimpse of the power that lays within the BEAM. Welcome to the dark side!

Laura M Castro

February 05, 2024
Tweet

More Decks by Laura M Castro

Other Decks in Programming

Transcript

  1. INTRODUCTION TO ERLANG & ELIXIR A gentle welcome to the

    BEAM community Porriño, February 6th, 2024 Laura M. Castro 1
  2. Who am I? Software Engineer (2003) PhD in Computer Science

    (2010) Professor: Software Architecture, Software Testing Researcher: industrial applications of functional programming (Erlang/Elixir), testing automation Advocate for women and gender perspective in tech 2
  3. Who am I? Software Engineer (2003) PhD in Computer Science

    (2010) Professor: Software Architecture, Software Testing Researcher: industrial applications of functional programming (Erlang/Elixir), testing automation Advocate for women and gender perspective in tech 2
  4. 5

  5. Functional languages ◦ Everything is a function ◦ Single assignment,

    no side effects ◦ No explicit memory management ◦ Runs on a multiplatform virtual machine Actor model 6
  6. Functional languages Actor model ◦ Everything is a process ◦

    Processes share nothing, communicate via async messages ◦ Processes focus on the happy path ◦ Processes supervise other processes 6
  7. Distribution Swiss-army knife Processes are extremely lightweight, their location is

    transparent, asynchronous communication is the norm, business and management concerns are kept separated. 6
  8. 7

  9. Born in 1986, released ’98 Concurrency-oriented ◦ Lightweight, isolated processes,

    message-passing communication Multiplatform VM Transparent distribution OTP behaviours Hot code swap 7
  10. Born in 1986, released ’98 Concurrency-oriented ◦ Lightweight, isolated processes,

    message-passing communication Multiplatform VM Transparent distribution OTP behaviours Hot code swap 7
  11. Born in 2011 Runs on the Erlang VM Can invoke

    Erlang code Born in 1986, released ’98 Concurrency-oriented ◦ Lightweight, isolated processes, message-passing communication Multiplatform VM Transparent distribution OTP behaviours Hot code swap 7
  12. Variable rebinding Dynamic typing Lazy collections Strings are not lists

    Pipeline operator Namespaces Polymorphism via protocols Single assignment Dynamic typing Eager evaluation 8
  13. Variable rebinding Dynamic typing Lazy collections Strings are not lists

    Pipeline operator Namespaces Polymorphism via protocols Single assignment Dynamic typing Eager evaluation 8
  14. 9

  15. 9

  16. HELLO, WORLD! 1 defmodule World do 2 3 def hello(who)

    do 4 IO.puts "Hello, #{who}!" 5 end 6 7 end 1 -module(world). 2 3 -export([hello/1]). 4 5 hello(Who) -> 6 io:format("Hello, ~p!~n", [Who]). 10
  17. HELLO, WORLD! 1 defmodule World do 2 3 def hello(who)

    do 4 IO.puts "Hello, #{who}!" 5 end 6 7 end 1 -module(world). 2 3 -export([hello/1]). 4 5 hello(Who) -> 6 io:format("Hello, ~p!~n", [Who]). 10
  18. HELLO, WORLD! 1 defmodule World do 2 3 def hello(who)

    do 4 IO.puts "Hello, #{who}!" 5 end 6 7 end 1 -module(world). 2 3 -export([hello/1]). 4 5 hello(Who) -> 6 io:format("Hello, ~p!~n", [Who]). 10
  19. SAMPLE MODULE 1 defmodule Boolean do 2 @moduledoc """ 3

    Simple pattern matching 4 """ 5 6 @doc """ 7 Operator ’not’. 8 9 ## Parameters 10 11 - value: boolean to be negated 12 13 """ 14 def b_not(true), do: false 15 def b_not(false), do: true 16 17 @doc """ 18 Operator ’and’. 19 20 ## Parameters 21 22 - value1 , value2: booleans to operate 23 24 """ 25 def b_and(true, true), do: true 26 def b_and(_, _), do: false 1 %%% @doc Simple pattern matching 2 %%% @end 3 -module(boolean). 4 5 -export([b_not/1, b_and/2]). 6 7 %% @doc Operator ’not’. 8 %% @end 9 b_not(true) -> false; 10 b_not(false) -> true. 11 12 %% @doc Operator ’and’. 13 %% @end 14 b_and(true, true) -> 15 true; 16 b_and(Value1, Value2) -> 17 false. 11
  20. BASIC TESTING 1 defmodule BooleanTest do 2 @moduledoc """ 3

    Simple pattern matching (tests) 4 """ 5 6 use ExUnit.Case 7 doctest Boolean 8 9 test "Boolean negation" do 10 assert Boolean.b_not(false) 11 refute Boolean.b_not(true) 12 end 13 14 test "Boolean AND" do 15 assert Boolean.b_and(true, true) 16 refute Boolean.b_and(true, false) 17 refute Boolean.b_and(false, true) 18 refute Boolean.b_and(false, false) 19 end 20 21 end 1 %%% @doc Simple pattern matching (tests) 2 %%% @end 3 -module(boolean_tests). 4 5 -include_lib("eunit/include/eunit.hrl"). 6 7 b_not_test() -> 8 ?assert(boolean:b_not(false)), 9 ?assertNot(boolean:b_not(true)). 10 11 b_and_test() -> 12 ?assert(boolean:b_and(true,true)), 13 ?assertNot(boolean:b_and(true,false)), 14 ?assertNot(boolean:b_and(false,true)), 15 ?assertNot(boolean:b_and(false,false)). 13
  21. SAMPLE MODULE (II) 1 defmodule Create do 2 @moduledoc """

    3 Creating lists 4 """ 5 6 def create(n) when n>0 do 7 acc_forward(1, n, []) 8 end 9 10 defp acc_forward(a, a, l), do: [a|l] 11 defp acc_forward(a, b, l), do: acc_forward(a, b-1, [b|l]) 12 13 def reverse_create(n) when n>0 do 14 acc_backwards(n, 1, []) 15 end 16 17 def acc_backwards(a, a, l), do: [a|l] 18 def acc_backwards(a, b, l), do: acc_backwards(a, b+1, [b|l]) 19 20 end 1 %%% @doc Creating lists 2 %%% @end 3 -module(create). 4 5 -export([create/1, reverse_create/1]). 6 7 create(N) when N>0 -> 8 acc_forward(1, N, []). 9 10 reverse_create(N) when N>0 -> 11 acc_backwards(N, 1, []). 12 13 acc_forward(A, A, L) -> [A | L]; 14 acc_forward(A, B, L) -> 15 acc_forward(A, B-1, [B | L]). 16 17 acc_backwards(A, A, L) -> [A | L]; 18 acc_backwards(A, B, L) -> 19 acc_backwards(A, B+1, [B | L]). 14
  22. POWERED-UP TESTING! 1 defmodule CreateProps do 2 @moduledoc """ 3

    Creating lists (test properties) 4 """ 5 6 use ExUnit.Case 7 use PropCheck 8 doctest Create 9 10 property "List creation" do 11 forall i <- positive_integer() do 12 Create.create(i+1) == Enum.to_list (1..i+1) 13 end 14 end 15 16 property "Reverse list creation" do 17 forall i <- positive_integer() do 18 Create.reverse_create(i+1) == Enum. reverse(Enum.to_list(1..i+1)) 19 end 20 end 21 22 end 1 %%% @doc Creating lists (test properties) 2 %%% @end 3 -module(prop_create). 4 -include_lib("proper/include/proper.hrl") 5 6 prop_create() -> 7 ?FORALL(I, positive_integer(), 8 create:create(I) == lists:seq(1, I)). 9 10 prop_reverse_create() -> 11 ?FORALL(I, positive_integer(), 12 create:reverse_create(I) == lists: reverse(lists:seq(1, I))). 15
  23. POWERED-UP TESTING! 1 defmodule CreateProps do 2 @moduledoc """ 3

    Creating lists (test properties) 4 """ 5 6 use ExUnit.Case 7 use PropCheck 8 doctest Create 9 10 property "List creation" do 11 forall i <- positive_integer() do 12 Create.create(i+1) == Enum.to_list (1..i+1) 13 end 14 end 15 16 property "Reverse list creation" do 17 forall i <- positive_integer() do 18 Create.reverse_create(i+1) == Enum. reverse(Enum.to_list(1..i+1)) 19 end 20 end 21 22 end 1 %%% @doc Creating lists (test properties) 2 %%% @end 3 -module(prop_create). 4 -include_lib("proper/include/proper.hrl") 5 6 prop_create() -> 7 ?FORALL(I, positive_integer(), 8 create:create(I) == lists:seq(1, I)). 9 10 prop_reverse_create() -> 11 ?FORALL(I, positive_integer(), 12 create:reverse_create(I) == lists: reverse(lists:seq(1, I))). 15
  24. SAMPLE MODULE (III) 1 defmodule Db do 2 @moduledoc """

    3 Database handling using lists 4 """ 5 6 def new(), do: [] 7 8 def write(db, key, value), do: [{key, value} | db] 9 10 def delete(db, key), do: do_delete(db, key, []) 11 12 defp do_delete([], _key, acc), do: acc 13 defp do_delete([{key, _value} | db], key, acc), do: acc ++ db 14 defp do_delete([next | db], acc), do: do_delete(db, key, [next | acc]) 1 %%% @doc Database handling using lists 2 %%% @end 3 -module(db). 4 5 -export([new/0, write/3, delete/2, read /2, match/2, destroy/1]). 6 7 new() -> 8 []. 9 10 write(Db, Key, Value) -> 11 [{Key, Value} | Db]. 12 13 delete(Db, Key) -> 14 do_delete(Db, Key, []). 15 16 do_delete([], _Key, Acc) -> 17 Acc; 18 do_delete([{Key, _Element} | Db], Key, Acc) -> 19 Acc ++ Db; 20 do_delete([Next={_DifferentKey , _Value} | Db], Key, Acc) -> 21 do_delete(Db, Key, [Next | Acc]). 16
  25. SAMPLE MODULE (III) 15 def read([], _), do: {:error, :

    not_found} 16 def read([{key, value} | _], key), do: {:ok, value} 17 def read([_ | db], key), do: read(key, db) 18 19 def match(db, value), do: do_match(db, value, []) 20 21 defp do_match([], _, acc), do: acc 22 defp do_match([{key, value} | db], value, acc), do: do_match(db, value, [key|acc]) 23 defp do_match([_ | db], value, acc), do : do_match(db, value, acc) 24 25 def destroy(_), do: :ok 26 27 end 22 read([], _Key) -> 23 {error, not_found}; 24 read([{Key, Value} | _Db], Key) -> 25 {ok, Value}; 26 read([_ | Db], Key) -> 27 read(Db, Key). 28 29 match(Db, Value) -> 30 do_match(Db, Value, []). 31 32 do_match([], _Value, Acc) -> 33 Acc; 34 do_match([{Key, Value} | Db], Value, Acc) -> 35 do_match(Db, Value, [Key | Acc]); 36 do_match([_ | Db], Value, Acc) -> 37 do_match(Db, Value, Acc). 38 39 destroy(_Db) -> 40 ok. 16
  26. BASIC PROCESS 1 defmodule DbProcess do 2 @moduledoc """ 3

    Database handling using a process 4 """ 5 6 def new(), do: spawn(DbProcess , :loop, [[]]) 7 8 def write(dbref, key, value) do 9 send(dbref, {:write, key, value}) 10 end 11 12 def delete(dbref, key) do 13 send(dbref, {:delete , key}) 14 end 15 16 def read(dbref, key) do 17 send(dbref, {:read, key, self()}) 18 receive do 19 {^dbref, :notfound} -> {:error, : instance} 20 {^dbref, value} -> {:ok, value} 21 end 22 end 1 %%% @doc Database handling using a process 2 %%% @end 3 -module(db_process). 4 5 -export([new/0, write/3, delete/2, read /2, match/2, destroy/1]). 6 7 new() -> 8 spawn(?MODULE, loop, [[]]). 9 10 write(DbRef, Key, Value) -> 11 DbRef ! {write, Key, Value}. 12 13 delete(DbRef, Key) -> 14 DbRef ! {delete, Key}. 15 16 read(DbRef, Key) -> 17 DbRef ! {read, Key, self()}, 18 receive 19 {DbRef, not_found} -> {error, not_found} 20 {DbRef, Value} -> {ok, Value}; 21 end. 17
  27. BASIC PROCESS 23 def match(dbref, value) do 24 send(dbref, {:match,

    value, self()}) 25 receive do 26 {^dbref, keys} -> keys 27 end 28 end 29 30 def destroy(dbref) do 31 send(dbref, :stop) 32 end 22 match(DbRef, Value) -> 23 DbRef ! {match, Value, self()}, 24 receive 25 {DbRef, Keys} -> Keys 26 end. 27 28 destroy(DbRef) -> 29 DbRef ! stop. 17
  28. BASIC PROCESS 33 def loop(db) do 34 receive do 35

    {:write, key, value} -> loop([{key, value} | db]) 36 {:delete , key} -> Keyword.delete(db , key) |> loop() 37 {:read, key, who} -> 38 send(who, {self(), Keyword.get(db , key, :not_found)}) 39 loop(db) 40 {:match, value, who} -> 41 send(who, {self(), Keyword.filter (db, fn {_key, value} -> value == element end)}) 42 loop(db) 43 :stop -> 44 :ok 45 _ -> 46 loop(db) 47 end 48 end 30 loop(Db) -> 31 receive 32 {write, Key, Value} -> 33 loop([{Key, Value} | Db]); 34 {delete, Key} -> 35 loop(lists:keydelete(Key, 1, Db)); 36 {read, Key, Who} -> 37 case lists:keyfind(Key, 1, Db) of 38 false -> 39 Who ! {self(), not_found}; 40 {Key, Value} -> 41 Who ! {self(), Value} 42 end, 43 loop(Db); 44 {match, Value, Who} -> 45 Keys = do_match(Db, Value, []), 46 Who ! {self(), Keys}, 47 loop(Db); 48 stop -> 49 ok; 50 _ -> 51 loop(Db) 52 end. 17
  29. BASIC OTP PROCESS 1 defmodule DbGenServer do 2 @moduledoc """

    3 Database handling using OTP 4 """ 5 6 use GenServer 7 8 def new() do 9 GenServer.start_link(__MODULE__ , []) 10 end 11 12 def write(dbref, key, value) do 13 GenServer.cast(dbref, {:write, key, element}) 14 end 15 16 def delete(dbref, key) do 17 GenServer.cast(dbref, {:delete, key}) 18 end 19 20 def read(dbref, key) do 21 GenServer.call(dbref, {:read, key}) 22 end 1 %% @doc Database handling using OTP 2 %% @end 3 -module(db_gen_server). 4 -behaviour(gen_server). 5 6 -export([new/0, write/3, delete/2, read /2, match/2, destroy/1]). 7 -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2, code_change/3]). 8 9 new() -> 10 gen_server:start_link(?MODULE, [], []). 11 12 write(DbRef, Key, Element) -> 13 gen_server:cast(DbRef, {write, Key, Element}). 14 15 delete(Key) -> 16 gen_server:cast(DbRef, {delete, Key}). 17 18 read(Key) -> 19 gen_server:call(DbRef, {read, Key}). 18
  30. BASIC OTP PROCESS 23 def match(dbref, value) do 24 GenServer.call(dbref,

    {:match, value }) 25 end 26 27 def destroy(dbref) do 28 GenServer.stop(dbref) 29 end 30 31 ## GenServer Callbacks 32 33 def init([]) do 34 {:ok, []} 35 end 36 37 def handle_cast({:write, key, element}, state) do 38 {:noreply , [{key, element} | state]} 39 end 40 def handle_cast({:delete , key}, state) do 41 {:noreply , Keyword.delete(state, key) } 42 end 20 match(DbRef, Value) -> 21 gen_server:call(DbRef, {match, Value}). 22 23 destroy(DbRef) -> 24 gen_server:stop(DbRef). 25 26 % gen_server callbacks 27 28 init([]) -> 29 {ok, []}. 30 31 handle_cast({write, Key, Element}, State) -> 32 {noreply , [{Key, Element} | State]}; 33 handle_cast({delete, Key}, State) -> 34 {noreply , lists:keydelete(Key, 1, State )}. 18
  31. BASIC OTP PROCESS 43 def handle_call({:read, key}, _from, state) do

    44 case Keyword.get(state, key, : not_found) do 45 :not_found -> {:reply, {:error, : not_found} , state} 46 value -> {:reply, {:ok, value }, state} 47 end 48 end 49 def handle_call({:match, value}, _from, state) do 50 {:reply, Keyword.filter(state, fn { _key, element} -> value == element end), state} 51 end 52 53 def handle_info(_msg, state) do 54 {:noreply , state} 55 end 56 57 def terminate(:normal , _state) do 58 :ok 59 end 60 61 end 35 handle_call({read, Key}, _From, State) -> 36 Reply = case lists:keyfind(Key, 1, State) of 37 false -> 38 {error, not_found}; 39 {Key, Value} -> 40 {ok, Value} 41 end, 42 {reply, Reply, State}; 43 handle_call({match, Value}, _From, State) -> 44 Keys = do_match(State, Value, []), 45 {reply, Keys, State}. 46 47 handle_info(_Info, State) -> 48 {noreply , State}. 49 50 terminate(normal, _State) -> 51 ok. 18
  32. WHAT ELSE...? Let it crash? Supervisor processes Implicit vs. explicit

    parallelism (Maps vs Tasks) Functions as first-class citizens (a.k.a. “pointers to functions”) Libraries and tooling 21
  33. 22

  34. TO TAKE BACK HOME TO KANSAS Erlang/Elixir/the BEAM: a key

    technology for an increasing number of parties Not just syntax: a paradigm shift Embrace the power of the actor model and take advantage of OTP ◦ Concurrency and distribution made easy 23
  35. 24