Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

LiveBook, functions, modules, pattern matching, maps & structs

Elixir_101_funs_modules_pattern_matching_structs.livemd

LiveBook, functions, modules, pattern matching, maps & structs

LiveBook

asdf: Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more

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:

> 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

Mermaid-JS

https://mermaid-js.github.io/mermaid/#/flowchart

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

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

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 Livebook keyboard shortcuts - part 2

Anonymous functions

fn num -> num + 7 end
(fn num -> num + 7 end).(10)
&(&1 + 7)
(&(&1 + 7)).(10)
(fn a, b, c -> a + b + c end).(30, 30, 40)
(&(&1 + &2 + &3)).(300, 300, 400)
add_7 = fn num -> num + 7 end
add_7.(8)
another_add_7 = &(&1 + 7)
another_add_7.(8)

Modules & named functions

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!

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
MyModule.add_2(4)
MyModule.add_3(9)
alias MyModule, as: M

M.add_3(5)
import MyModule

add_3(7)

The pipeline operator, |>

7 |> add_3() |> add_2()
7 |> (&add_3/1).() |> (&add_2/1).()
7 |> (&M.add_3/1).() |> (&M.add_2/1).()
7 |> (&MyModule.add_3/1).() |> (&MyModule.add_2/1).()
7 |> (&(&1 + 3)).() |> (&(&1 + 2)).()
30 |> (&M.add_3/2).(20)
7
|> (&(&1 + 3)).()
|> (&(&1 + 2)).()
7
|> add_3()
|> add_2()

Multi-clause functions with pattern-matching

# 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
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

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
MyPrint.print("Yay!")
MyPrint.print(:Hooray!)
MyPrint.print(%{Hello: "world"})

Maps & Structs

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()
CelticsPlayers.players()
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()
# 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
Player.__struct__()
Player.__struct__().__struct__()
defimpl Inspect, for: Player do
  def inspect(player, _opts) do
    ""
  end
end
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()
# This will fail because bob is missing keys required by the Player struct

bob = %Player{fname: "Bob"}
# 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()
# StructCeltics.team() |> IO.inspect([{:structs, false}])
StructCeltics.team() |> IO.inspect(structs: false)

Testing

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()
Mix.install([{:recursive_selective_match, "~> 0.2.6"}])
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()