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

Elixir

Norman
October 26, 2018

 Elixir

Presentation introducing Elixir (and functional programming) to our team.

Norman

October 26, 2018
Tweet

More Decks by Norman

Other Decks in Programming

Transcript

  1. D I S T R I B U T E

    D F U N C T I O N A L P R O G R A M M I N G W I T H N O R M A N T H O M A S 2 0 1 8 - 1 0 - 2 6 E L I X I R A N D O T P
  2. O V E R V I E W • Current

    state of affairs • Functional programming • Elixir basics • OTP • Deployment • Outlook
  3. W H E R E D O W E S

    TA N D ? C++ Java JavaScript Ruby Python Rust Go Scala OCaml Haskell Erlang Elixir Lisp Elm F# Swift Kotlin Akka Redux Smalltalk Clojure Scheme Racket Common Lisp C
  4. W H E R E D O W E S

    TA N D ? C++ Java JavaScript Ruby Python Rust Go Scala OCaml Haskell Erlang Elixir Lisp Elm F# Swift Kotlin Akka Redux Smalltalk Clojure Scheme Racket Common Lisp C ▪Influencers
  5. S H O R T C O M I N

    G S O F C U R R E N T S TAT E • Mutable by default • Side-effects all over the place / unpredictable • Unexpected error? ⏩ End of program • Shared mutable state • Imperative vs. declarative • Functional Programming; What? Why? When?
 Robert C. Martin @ NDC 2014 null, None, undefined or AttributeError: 'NoneType' object has no attribute ‘width'
  6. L E T ’ S P L AY A L

    I T T L E G A M E
  7. T H E R E ’ S M O R

    E … get_list_status(params, arr, query_result); return_items(arr, query_result); query_result = save_ids(ids); % * actual lines from a repo
  8. W H AT D O E S T H I

    S C O D E D O ? a = [1, 2, 3] b = a.concat(4) // a = ... ? b = ... ? b = a.push(4) // a = ... ? b = ... ? b = a.reverse() // a = ... ? b = ... ? b = a.sort() // a = ... ? b = ... ?
  9. W H AT D O E S T H I

    S C O D E D O ? a = [1, 2, 3] b = a.concat(4) // a = [1, 2, 3]; b = [1, 2, 3, 4] b = a.push(4) // a = ... ? b = ... ? b = a.reverse() // a = ... ? b = ... ? b = a.sort() // a = ... ? b = ... ?
  10. W H AT D O E S T H I

    S C O D E D O ? a = [1, 2, 3] b = a.concat(4) // a = [1, 2, 3]; b = [1, 2, 3, 4] b = a.push(4) // a = [1, 2, 3, 4]; b = 4 b = a.reverse() // a = ... ? b = ... ? b = a.sort() // a = ... ? b = ... ?
  11. W H AT D O E S T H I

    S C O D E D O ? a = [1, 2, 3] b = a.concat(4) // a = [1, 2, 3]; b = [1, 2, 3, 4] b = a.push(4) // a = [1, 2, 3, 4]; b = 4 b = a.reverse() // a = [4, 3, 2, 1]; b = [4, 3, 2, 1] b = a.sort() // a = ... ? b = ... ?
  12. W H AT D O E S T H I

    S C O D E D O ? a = [1, 2, 3] b = a.concat(4) // a = [1, 2, 3]; b = [1, 2, 3, 4] b = a.push(4) // a = [1, 2, 3, 4]; b = 4 b = a.reverse() // a = [4, 3, 2, 1]; b = [4, 3, 2, 1] b = a.sort() // a = [1, 2, 3, 4]; b = [1, 2, 3, 4]
  13. “If a developer must consider the implementation of a component

    in order to use it, the value of encapsulation is lost.” Eric Evans *DDD guy
  14. P R O S P E C T ? •

    Immutability as a default • Concurrency out of the box • Scalability • Resilience, high availability • Declarative problem-solving • Focus on the what, not how
  15. N O TA B L E U S E R

    S At Discord we have been very happy using Elixir/Erlang as a core technology of our backend services. We are pleased to see additions such as GenStage that build on the rock solid technologies of Erlang/OTP. *5 million concurrent users *2 million concurrent users on a single server
  16. *in Elixir F U N C T I O N

    A L P R O G R A M M I N G
  17. P R I N C I P L E S

    • Immutability • No side effects • Referential transparency • Pattern matching • Lazy evaluation • No loops ➿, yes, none of the following: • for, while, do-while, repeat-until, etc. • First class functions • No shared memory between processes • yes: no locks, mutexes, semaphores etc. • yes: no threads • Communication via messages • Let it crash! • Exceptions only for unexpected, exceptional cases ⚡ * C++ Meetup “Almost no raw loops” * CppCon “Free your Functions”
 * “Structure and Interpretation of Computer Programs” J & J Sussman, 1979, MIT Press
  18. O O P V S F P OOP FP paradigm

    everything is an object plain structs reusability through inherticance composition side effects yes no / avoided look-n-feel arr.append([4, 5, 6]) Array.append(arr, [4, 5, 6]) preference statement expression overloading through types / classes pattern matching state management in object / class instance process mindset messages to objects transformation of data concurrency manual work out of the box
  19. “I call it my billion-dollar mistake. It was the invention

    of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object-oriented language.” Tony Hoare *inventor of null
  20. N O N - D E V E X A

    M P L E : P H O T O S H O P • FP: Adjustment layers • non-destructive transformation • don’t mutate input • composable • OO: Image adjustment & Filters • destructive, mutate original input
  21. T H E B A S I C S •

    Elixir started 2012 • compiles to Erlang byte code • Erlang from 1986/1987 • BEAM VM • dynamic, strict typing • Recursion + HOFs instead of loops • Light-weight, share-nothing concurrency • Railway-oriented programming • Lisp-like meta-programming at compile-time
 • Native support for • concurrency / async • seamless parallel processing • lazy stream-processing • Built-in • dependency management • unit tests • code formatter • remote debugging
  22. • Declarative (what vs. how) • Scalability / Concurrency •

    no side effects = runnable in parallel • Testability / Debuggability* • functions only depend on input parameters • Simplicity • Composability / Reusability P R I N C I P L E S * yes, this word actually exists
  23. T Y P E S • Atom
 :ok, :error, :timeout,

    :fantasy • String, Binary, Regex
 “some string”, <<126, 127>>, ~r/^.*$/ • Integer • Float • Date, DateTime, Time, Date.Range • PID • Function • Exception • Stream • Collections • Tuple {1, 2, 3} • List, Keyword List [1, 2, 3], [k1: 1, k2: 2] • Map %{ “key” => “value” } • MapSet • Struct • Record all are deeply immutable
  24. | >

  25. P I P E S A R E FA M

    I L I A R > du -h -d 0 node_modules/* | grep "M\s" | gsed 's/\s*\([0-9]\+\(\.[0-9]\+\)\?\)M\s\+\(.\+\)/\1 MB\t\3/' | sort -n | tail 5.1 MB node_modules/fbjs 6.3 MB node_modules/@babel 6.4 MB node_modules/less 7.3 MB node_modules/core-js 9.4 MB node_modules/faker 13 MB node_modules/@ant-design 16 MB node_modules/rxjs 51 MB node_modules/@material-ui 52 MB node_modules/antd 201 MB node_modules/puppeteer > awk '$1 !~ /(EBOOK|BOOK)/{ print $1 }' dotbooks_sales.csv | sort | uniq -c 59724 ebook 963 epub 1 product > awk '$4 == "EUR" { print $2, $3 }' dotbooks_sales.csv | tail -n +2 | awk '{q+=$1; amount+=$2} END {print q, amount}’ 2163869 4.84478e+06
  26. P I P E S MySQL mysql = ... Browser

    browser = ... html = browser.visit(page) print(html) data = scrape(html) mysql.store_to_db(data) browser |> visit(page) |> IO.inspect # easy debugging / logging |> scrape(:metrics) |> MySQL.store_to_db()
  27. T R A N S F O R M AT

    I O N A P P L I C AT I O N Task: find the first n prime numbers in a range from 2 to 1000 bool is_prime(const int num) { ... } std::vector<int> result; for (int i = 2; i < 1000 && result.size() < n; i++) { if (is_prime(i)) result.push_back(i); } is_prime = fn num -> ... end 2..1000 |> Stream.filter(&is_prime/1) |> Enum.take(n) const is_prime = num => { ... } result = [...Array(1000).keys()] .filter(is_prime).slice(0, n) ≠
  28. T R A N S F O R M AT

    I O N A P P L I C AT I O N Task: find the first n prime numbers in a range from 2 to 1000 bool is_prime(const int num) { ... } std::vector<int> result; for (int i = 2; i < 1000 && result.size() < n; i++) { if (is_prime(i)) result.push_back(i); } is_prime = fn num -> ... end 2..1000 |> Stream.filter(&is_prime/1) |> Enum.take(n) is_prime = fn num -> ... end 1..1000 |> Flow.from_enumerable |> Flow.filter(&is_prime/1) |> Enum.take(n)
  29. A N O T H E R E X A

    M P L E std::vector<Vlb_Kat> search_vlb_kats(const size_t limit) const { IDVector all_vlb_kat_ids; SearchIDs<Vlb_Kat>::search(all_vlb_kat_ids); NGString query = ctx_.url().parameters().get_parameter_single("q").lowercase(); std::vector<Vlb_Kat> vlb_kats; size_t matches = 0; for(auto const vlb_kat_id : all_vlb_kat_ids) { if(matches >= limit) break; Vlb_Kat vlb_kat; if(vlb_kat.load(vlb_kat_id, LoadContextCache()) && vlb_kat.get_bod_supported() == Vlb_Kat::BOD_SUPPORTED::YES) { if(NGString::npos != vlb_kat.get_description().lowercase().find(query)) { vlb_kats.push_back(vlb_kat); ++matches; } } } return vlb_kats; } defmodule Request.RPC.AutoSearch do def search_vlb_kat(request_params, limit) do query = request_params["q"] |> String.downcase() DB.search("vlb_kat") |> Stream.map(&load_vlb_kat/1) |> Stream.filter(fn vlb_kat -> vlb_kat.bod_supported end) |> Stream.filter(fn vlb_kat -> vlb_kat.description |> String.downcase() |> String.contains?(query) end) |> Enum.take(limit) end end
  30. E L I X I R V S C +

    + // lambda auto println = [](const char *message){ std::cout << message << std::endl; }; # lambda println = fn message -> IO.puts message end // fold expressions (C++17) bool all(bool... args) { return (... && args); } # Enum.all?([true, true, false, true]) // “filter” std::copy_if(x.begin(), x.end(), std::back_inserter(y), predicate); x |> Enum.filter &predicate/1 Enum.filter(x, &predicate/1)
  31. E L I X I R V S P Y

    T H O N config = { "host": "api.op.com", "api_key": "1_1R_3" } create_url = lambda: f"https://{config['host']}/rpc/me?access_token={config['api_key']}" config.update({ "api_key": "1_4R_9" }) url = create_url() # https://api.op.com/rpc/me?access_token=1_4R_9 config = %{ host: "api.op.com", api_key: "1_1R_3" } create_url = fn -> "https://#{config.host}/rpc/me?access_token=#{config.api_key}" end config = Map.put(config, :api_key, "1_4R_9") url = create_url.() # https://api.op.com/rpc/me?access_token=1_1R_3
  32. E L I X I R V S P Y

    T H O N Python Elixir 1 = 1 SyntaxError: can't assign to literal 1 a, b = 1, 2 a = 1 b = 2 ** (SyntaxError) iex:1: syntax error before: ',' [1,2,3] == [1,2,3] True true “foo” in [“foo”, “bar”] True true “foo” == “foo” True true not not “” / !!”” False true "" or False / "" || false False “” 0 or True / 0 || true True 0 1 == true True false 2 == true False false true + true 2 ArithmeticError
  33. E L I X I R V S E S

    6 / 7 / 8 var people = [ { name: 'Alice', age: 21 }, { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ]; // conventional function groupBy(objectArray, property) { return objectArray.reduce(function (acc, obj) { var key = obj[property]; if (!acc[key]) { acc[key] = []; } acc[key].push(obj); return acc; }, {}); } // lodash _.groupBy(people, p => p.age) people = [ %{name: "Alice", age: 21}, %{name: "Max", age: 20}, %{name: "Jane", age: 20} ] people |> Enum.group_by fn item -> item.age end ES6 Elixir 1 + “1” + 1 “111” ArithmeticError “1” - 0 1 ArithmeticError {} + [] 0 ArithmeticError [1,2,3] == [1,2,3] false true [1,2,3] === [1,2,3] false true “foo” in [“foo”, “bar”] false [“foo”, “bar”] .includes("foo") true “foo” === “foo” true true !!”” false true true + true 2 ArithmeticError
  34. E L I X I R V S E S

    6 / 7 / 8 ( C O N T D ) • dependency hell thomas@thomas:~/git/trunk/react (stable•) $ grep "^\s\{1,4\}\"[a-zA-Z0-9_-.]\+\": {" package-lock.json | wc -l 935 thomas@thomas:~/git/trunk/react (stable•) $ du -h -d0 node_modules 761M node_modules added 413 packages from 135 contributors, removed 98 packages, updated 436 packages and audited 7315 packages in 64.839s found 406 vulnerabilities (339 low, 42 moderate, 20 high, 5 critical) run `npm audit fix` to fix them, or `npm audit` for details • immutablejs: 139 KB • lodash: 115 KB • redux: 3.5 MB • flow: 5.6 MB • webpack to tame the beast
  35. E L M ? hmmm… -- CARD VIEW taskItemView :

    Int -> Task -> Html Msg taskItemView index task = li [ class "task-item", attribute "draggable" "true", onDragStart <| Move task, attribute "ondragstart" "event.dataTransfer.setData('text/plain', '')" ] [ text task.name , button [ class "btn-delete" , onClick <| Delete task.name ][ text "+" ] ] -- COLUMN VIEW taskColumnView : String -> List Task -> Html Msg taskColumnView status list = div [ class <| "category " ++ String.toLower status, attribute "ondragover" "return false", onDrop <| DropTask status ] [ h2 [] [ text status ], span [] [ text (toString (List.length list) ++ " item(s)") ], ul [] (List.indexedMap taskItemView list) ]
  36. I M I TAT I O N S I N

    T H E W I L D Python Javascript C++ Functional Programming OSlash / toolz / pyfunctional / … ramda / Sanctuary FunctionalPlus Immutability ⛔ / pyrsistent ⛔ / immutable.js ✅ Pipe Operator ⛔ this, this, this and this… ⏳ official proposal ⛔ imitation Pattern Matching ⛔ decorators or outdated module ✅ ⛔ Mach7 or Patterns Monads ⛔ OSlash / PyMonad ⛔ MonetJS ✅ std::optional / neither Functors ✅ ✅ ✅ map / filter / reduce ✅ ✅ ✅ std::transform / std::copy_if… Currying / Partial Application manual / ✅ manual or ramda / ✅ ⛔ via templates Tail Call Optimization ⛔ ⛔ ⛔
  37. PAT T E R N M AT C H I

    N G def fac(1), do: 1 def fac(n), do: n * fac n - 1 [1, a, 3] = [1, 2, 3] [a | _] = [1, 2, 3] [_ | rest] = [1, 2, 3] [a, b | _] = [1, 2, 3] [a | [b | _]] = [1, 2, 3] %{config: %{destination: {"https", host, 443}}} = %{config: %{destination: {"https", "api.op.com", 443}}} %{config: %{destination: {"http", host, 80 }}} = %{config: %{destination: {"https", "api.op.com", 443}}} %{config: %{destination: {_, host, _ }}} = %{config: %{destination: {"https", "api.op.com", 443}}}
  38. R E C U R S I O N def

    deep_get(%{} = m, [h | t]) do m |> Map.fetch!(h) |> deep_get(t) end def deep_get([_ | _] = m, [h | t]) when is_integer(h) do m |> Enum.fetch!(h) |> deep_get(t) end def deep_get([_ | _] = m, [h | t]) when is_atom(h) do m |> Keyword.fetch!(h) |> deep_get(t) end def deep_get(m, []) do m end def fun do map = %{ "user" => %{ "id" => 123, "name" => %{ "first" => “Siglinde", "last" => “Petersen" }, "documents" => [123, 456, 789] } } first_name = deep_get(map, ["user", "name", "first"]) first_document = deep_get(map, ["user", "documents", 0]) {first_name, first_document} end
  39. S O , W H Y N O T P

    L A I N E R L A N G T H O U G H ? ⚠ It has no pipe operator ⚠ !Es hat keinen Pipe-Operator! !Não tem operador de tubulação! !Вона не має трубного оператора! !ሏ຅༗؅ಓࢉࢠ!
  40. D R A W B A C K S •

    Not well suited for heavy, mathematical operations • Not much machine learning
  41. O T P O P E N T E L

    E C O M P L AT F O R M
  42. E R L A N G H E R I

    TA G E • Robust • Fault tolerant • Scalable • Distributed • since the 1980s
  43. P R I N C I P L E S

    • Processes, processes everywhere • Processes communicate via messages • Implementation of patterns revolving around process lifecycle and management • Every process is a supervisor or worker • Application is a tree of processes • Adaptable generic solution for specific problem send pid, message receive do message -> do_something(message) end spawn fn -> IO.puts “Heavy duty!” end
  44. P R O C E S S L I N

    K S iex> spawn_monitor fn -> IO.puts "Hello from #{inspect Node.self}" end Hello from :miranda@io {#PID<0.118.0>, #Reference<0.3297442424.34078724.196482>} iex> flush {:DOWN, #Reference<0.3297442424.34078724.196482>, :process, #PID<0.118.0>, :normal} • spawn
 • spawn_link
 • spawn_monitor
  45. P E R F O R M A N C

    E • Elixir/Erlang processes are cheap • 1µs to spawn—yes, µs, not ms—for up to 3000 procs • can handle over 100k procs on a single machine • 0.8µs for sending a message for up to 30k procs
  46. G E N E R I C V S S

    P E C I F I C • Problem may be domain specific, technical challenge most likely not • Reinvention addiction • challenging • fun • waste of time
  47. O T P A P P L I C AT

    I O N • Similar to a service within micro-services, often smaller • Functionality can be started/stopped as a unit • think: systemd units • Reusable, isolated components
  48. S U P E R V I S O R

    S • Starts / stops / monitors other processes • Decides what happens if a sub-process fails • Has restart strategies • No other responsibilities
  49. W O R K E R S • Do the

    actual work • May or may not be supervised • Can crash • may or may not be restored after a crash • Are managed by supervisor
  50. O T P PAT T E R N S /

    T O O L S • Battle-tested collection of
 generic implementations: • GenServer • GenTCP • GenUDP • GenEvent • GenStatem • GenStage* • Tasks • (D)ETS • Mnesia
  51. G E N S E R V E R •

    Generic Server • Provides • call (synchronous) • cast (asynchronous) • info • can spawn new process for each request • can provide worker pool • can do load balancing
  52. G E N S TA G E P R O

    D U C E R P R O D U C E R C O N S U M E R P R O D U C E R C O N S U M E R P R O D U C E R C O N S U M E R C O N S U M E R • Pipeline
 • Back-pressure
 • Consumer pulls data
  53. E T S / D E T S / M

    N E S I A • ETS = Erlang Term Storage • in-memory ephemeral storage (key-value / cache) • single-node • DETS = Disk-based ETS • Mnesia • powerful, distributed relational database • multi-node
  54. O T P R E L E A S E

    S • Builds self-contained package • Highly configurable • Versioning • Hot up/downgrades • code_change event/callback • Distributed applications • Rollback possible
  55. D E P L O Y M E N T

    • Declare settings for dev, test and prod • Building and deploying via Docker possible • tar ball is generated • copy tar ball to target machine
  56. D I S T R I B U T I

    O N • Support for distribution / clustering from day 1 • Processes can be run anywhere, on any machine • Multiple nodes can be connected • Support for load balancing • Support for “leader”, fail-over, take-over across nodes • Ability to capture state when process crashes
  57. L E T ’ S S E T U P

    A C L U S T E R Node.connect(:"[email protected]")
  58. L E T ’ S T RY I T >

    iex --sname n1 --cookie mysecret123 iex> Node.list iex> Node.connect :n2@io iex> Process.register self(), :carla iex> send {:anastacia, :n2@io}, {"Yo, Anastacia!", self()} iex> flush > iex --sname n2 --cookie mysecret123 iex> Node.list iex> Node.list iex> Process.register self(), :anastacia iex> receive do {msg, from} -> IO.puts "You've got mail! from #{inspect from}: ‘#{msg}'" :timer.sleep(3_000) send from, “back to you!” end iex> flush * possible to register global names
  59. D I S T R I B U T I

    O N P O W E R E D B Y E L I X I R • Respond to demand • DynamicSupervisor • set bounds, i.e. min/max workers • Distribute workload across nodes • Automated, reactive cluster formation, discovery and healing
 via swarm / libcluster • Asynchronicity* permits easier monitoring / introspection of application state • Encryption using TLS * tongue twister
  60. S T I L L • Even with Erlang/Elixir,
 distributed

    systems don’t fall from the sky • But the Erlang ecosystem provides powerful tools
 to manage the complexity, and to focus on the actual task at hand
  61. T O O L I N G :observer monitoring mix

    formatter code formatter dialyzer code analyzer credo linter inch documentation coverage IEx REPL + debugging ExUnit testing PropEr / PropCheck property testing benchee benchmark
  62. W H E R E TO G O F R

    O M H E R E ?
  63. R E S O U R C E S •

    Websites • Elixir Lang • HexDocs • Elixir School • Online Courses • Developing With Elixir/OTP
 • Literature • Introducing Elixir (2nd edition) • Programming Elixir 1.6 • Mastering Elixir • Designing for Scalability with Erlang/OTP • Youtube channels • ElixirConf • CodeSync • Erlang Solutions
  64. D I ST R I B U T E A

    L L T H E T H I N G S