# LiveBook, functions, modules, pattern matching, maps & structs ## LiveBook * https://livebook.dev/ * https://github.com/livebook-dev/livebook * [Livebook v0.5: flowcharts, chat apps, multiplayer games, and custom widgets!](https://www.youtube.com/watch?v=5tLsBwAjOo0) * https://github.com/livebook-dev/kino * https://mermaid-js.github.io/mermaid/# asdf: Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more * https://github.com/asdf-vm/asdf * https://asdf-vm.com/guide/getting-started.html * http://asdf-vm.com/manage/commands.html * Linux + Mac... NOT Windows ## It's Markdown It's markdown, so you can... *italicize* or **bold** text or even ***bold and italicize*** text Mark text as `code` Create links: Create bullet lists: * [Markdown Guide](https://www.markdownguide.org/basic-syntax/) > And create blockquotes > > > And nest blockquotes, like this > You can put bullet lists inside blockquotes: > > * #1 > * #2 Create code blocks: ``` 10 PRINT "Hello, World" 20 GOTO 10 ``` and ``` ``` Because it's Markdown, you can also view it as a text file. THIS file is viewable at (the 2nd link being a raw file .livemd): * * I've made use of these being Markdown files to `diff version1.livemd version2.livemd` They also work great with version control systems, like Git. ## Livebook.dev ![livebook.dev.png](images/livebook.dev.png)
## Mermaid-JS
https://mermaid-js.github.io/mermaid/#/flowchart ```mermaid flowchart TD A[Start] --> B{Is it?}; B -->|Yes| C[OK]; C --> D[Rethink]; D --> B; B ---->|No| E[End]; ``` https://mermaid-js.github.io/mermaid/#/sequenceDiagram ```mermaid sequenceDiagram par Alice to Bob Alice->>Bob: Hello guys! and Alice to John Alice->>John: Hello guys! end Bob-->>Alice: Hi Alice! John-->>Alice: Hi Alice! ``` https://mermaid-js.github.io/mermaid/#/entityRelationshipDiagram ```mermaid erDiagram CUSTOMER ||--o{ ORDER : places CUSTOMER { string name string custNumber string sector } ORDER ||--|{ LINE-ITEM : contains ORDER { int orderNumber string deliveryAddress } LINE-ITEM { string productCode int quantity float pricePerUnit } ``` ## Show keyboard shortcuts ![Livebook keyboard shortcuts - part 1](images/livebook_keyboard_shortcuts2.png) ![Livebook keyboard shortcuts - part 2](images/livebook_keyboard_shortcuts.png) ## Anonymous functions * https://elixirschool.com/en/lessons/basics/functions/ * https://elixir-lang.org/getting-started/modules-and-functions.html * https://elixircasts.io/intro-to-elixir-functions * https://devato.com/post/getting-started-with-elixir-functions ```elixir fn num -> num + 7 end ``` ```elixir (fn num -> num + 7 end).(10) ``` ```elixir &(&1 + 7) ``` ```elixir (&(&1 + 7)).(10) ``` ```elixir (fn a, b, c -> a + b + c end).(30, 30, 40) ``` ```elixir (&(&1 + &2 + &3)).(300, 300, 400) ``` ```elixir add_7 = fn num -> num + 7 end ``` ```elixir add_7.(8) ``` ```elixir another_add_7 = &(&1 + 7) ``` ```elixir another_add_7.(8) ``` ## Modules & named functions ```elixir defmodule MyModule do end ``` A module's name is its identity. Module name does NOT need to conform to any specific file name, directory hierarchy, or namespacing convention! ```elixir defmodule MyModule do def add_2(num), do: 2 + num def add_3(num) do 3 + num end def add_3(num, num2) do 3 + num + num2 end end ``` ```elixir MyModule.add_2(4) ``` ```elixir MyModule.add_3(9) ``` ```elixir alias MyModule, as: M M.add_3(5) ``` ```elixir import MyModule add_3(7) ``` ## The pipeline operator, |> ```elixir 7 |> add_3() |> add_2() ``` ```elixir 7 |> (&add_3/1).() |> (&add_2/1).() ``` ```elixir 7 |> (&M.add_3/1).() |> (&M.add_2/1).() ``` ```elixir 7 |> (&MyModule.add_3/1).() |> (&MyModule.add_2/1).() ``` ```elixir 7 |> (&(&1 + 3)).() |> (&(&1 + 2)).() ``` ```elixir 30 |> (&M.add_3/2).(20) ``` ```elixir 7 |> (&(&1 + 3)).() |> (&(&1 + 2)).() ``` ```elixir 7 |> add_3() |> add_2() ``` ## Multi-clause functions with pattern-matching ```elixir # Greeting.greet/1 has three function clauses defmodule Greeting do def greet(:professor_williams), do: "Hello, Professor Williams" def greet(:mom), do: "Hi, Mom!" def greet(:best_friend), do: "Yo! Wassup?!?!" def greet(something_else), do: "Sorry, I don't know you." end ``` ```elixir alias Greeting, as: G G.greet(:professor_williams) |> IO.inspect() G.greet(:mom) |> IO.inspect() G.greet(:best_friend) |> IO.inspect() G.greet("Yo, wassup?") |> IO.inspect() ``` ## Guard Clauses ```elixir defmodule MyPrint do def print(thing) when is_binary(thing) do thing |> IO.puts() end def print(thing) when is_atom(thing) do thing |> Atom.to_string() |> IO.puts() end def print(thing) when is_map(thing) do # thing |> Enum.map_join(", ", fn {k, v} -> "#{k}, #{v}" end) |> IO.puts() thing |> Enum.map(fn {k, v} -> "#{k}, #{v}" end) |> Enum.join(" ,") |> IO.puts() end end ``` ```elixir MyPrint.print("Yay!") ``` ```elixir MyPrint.print(:Hooray!) ``` ```elixir MyPrint.print(%{Hello: "world"}) ``` ## Maps & Structs ```elixir defmodule CelticsPlayers do def bird, do: %{fname: "Larry", lname: "Bird", number: 33, position: :forward} def parish, do: %{fname: "Robert", lname: "Parish", number: 0, position: :center} def ainge, do: %{fname: "Danny", lname: "Ainge", number: 44, position: :guard} def johnson, do: %{fname: "Dennis", lname: "Johnson", number: 3, position: :guard} def mchale, do: %{fname: "Kevin", lname: "McHale", number: 32, position: :forward} def walton, do: %{fname: "Bill", lname: "Walton", number: 5, position: :center} def players, do: [bird(), parish(), ainge(), johnson(), mchale(), walton()] end CelticsPlayers.bird() ``` ```elixir CelticsPlayers.players() ``` ```elixir defmodule Celtics do import CelticsPlayers def team, do: %{ forwards: players() |> Enum.filter(&(&1.position == :forward)), centers: players() |> Enum.filter(&(&1.position == :center)), guards: players() |> Enum.filter(&(&1.position == :guard)) } end Celtics.team() ``` ```elixir # https://elixir-lang.org/getting-started/structs.html # https://elixircasts.io/intro-to-structs defmodule Player do @enforce_keys [:fname, :lname, :number, :position] defstruct [:fname, :lname, :number, :position, :team] end ``` ```elixir Player.__struct__() ``` ```elixir Player.__struct__().__struct__() ``` ```elixir defimpl Inspect, for: Player do def inspect(player, _opts) do "" end end ``` ```elixir defmodule StructCeltics do import CelticsPlayers # @players players() @struct_players players() |> Enum.map(&struct(Player, &1)) def team, do: %{ forwards: @struct_players |> Enum.filter(&(&1.position == :forward)), centers: @struct_players |> Enum.filter(&(&1.position == :center)), guards: @struct_players |> Enum.filter(&(&1.position == :guard)) } end StructCeltics.team() ``` ```elixir # This will fail because bob is missing keys required by the Player struct bob = %Player{fname: "Bob"} ``` ```elixir # https://hexdocs.pm/elixir/1.13/Map.html defmodule StructCeltics do import CelticsPlayers # @struct_players players() |> Enum.map(&(struct(Player, &1) |> Map.put(:team, :celtics))) @struct_players players() |> Enum.map(&struct(Player, &1)) |> Enum.map(&Map.put(&1, :team, :celtics)) def team, do: %{ forwards: @struct_players |> Enum.filter(&(&1.position == :forward)), centers: @struct_players |> Enum.filter(&(&1.position == :center)), guards: @struct_players |> Enum.filter(&(&1.position == :guard)) } end # StructCeltics.team() |> inspect(structs: false) StructCeltics.team() |> inspect() ``` ```elixir # StructCeltics.team() |> IO.inspect([{:structs, false}]) StructCeltics.team() |> IO.inspect(structs: false) ``` ## Testing ```elixir ExUnit.start(autorun: false) defmodule TestCeltics do use ExUnit.Case, async: true require StructCeltics test "McHale is an '85-86 Celtic" do celtics = StructCeltics.team() players = celtics[:forwards] ++ celtics[:centers] ++ celtics[:guards] # players |> IO.inspect(structs: false) # TEST FAILS: # kevin = %Player{lname: "McHale", fname: "Kevin", number: 32, position: :forward} # TEST PASSES: kevin = %Player{ lname: "McHale", fname: "Kevin", number: 32, position: :forward, team: :celtics } # kevin |> IO.inspect(structs: false) assert kevin in players # refute kevin not in players end end ExUnit.run() ``` ```elixir Mix.install([{:recursive_selective_match, "~> 0.2.6"}]) ``` ```elixir ExUnit.start(autorun: false) defmodule TestCeltics2 do use ExUnit.Case, async: true require StructCeltics alias RecursiveSelectiveMatch, as: RSM test "McHale is an '85-86 Celtic" do celtics = StructCeltics.team() players = celtics[:forwards] ++ celtics[:centers] ++ celtics[:guards] kevin = %Player{lname: "McHale", fname: "Kevin", number: 32, position: :forward} # kevin = %Player{ # lname: "McHale", # fname: "Kevin", # number: 32, # position: :forward, # team: :celtics # } assert RSM.includes?(kevin, players) end end ExUnit.run() ```