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

Control Structures

control-structures.livemd

Control Structures

Mix.install([
  {:kino, "~> 0.12.2"},
  {:vega_lite, "~> 0.1.6"},
  {:kino_vega_lite, "~> 0.1.10"}
])

Introduction

alias VegaLite, as: Vl

If and Else

An if construction evaluates to a value. If the else path is omitted it will evaluate til nil:

fun_if = fn c ->
  if c do
    1
  end
end

{
  fun_if.(true),
  fun_if.(false)
}

But it can also be included, like this:

fun_ifelse = fn c ->
  if c do
    1
  else
    2
  end
end

{
  fun_ifelse.(true),
  fun_ifelse.(false)
}

Cond

if constructions are often chained. While they can be nested, it is nicer to use a cond construction. In such a construction, a number of expressions are evaluated in order until one of them evaluates to true, and then the corresponding entry is the result of the cond construction.

a = 1
b = 2
c = 3

cond do
  a == 2 -> :first
  b < a -> :second
  false -> :third
  a + b == c -> :fourth
  true -> :fifth
end

Case

A case construct evaluates to the result of the branch associated with the first matching pattern. Underscore is a pattern that matches anything.

Example:

color = fn input ->
  case input do
    "red" -> {1, 0, 0}
    "green" -> {0, 1, 0}
    "blue" -> {0, 0, 1}
    value when is_float(value) -> {value, value, value}
    _ -> {1, 1, 1}
  end
end

{
  color.("red"),
  color.("blue"),
  color.(42),
  color.(0.3),
  color.(self())
}

Larger Example

Some average heights from Wikipedia:

heights = %{
  {"Denmark", :male} => 180.4,
  {"Denmark", :female} => 167.2,
  {"Greece", :male} => 177,
  {"Greece", :female} => 165,
  {"Finland", :male} => 178.9,
  {"Finland", :female} => 165.3,
  {"New Zealand", :male} => 177,
  {"New Zealand", :female} => 164,
  {"Netherlands", :male} => 183.8,
  {"Netherlands", :female} => 170.7,
  {"North Korea", :male} => 165.6,
  {"North Korea", :female} => 154.9,
  {"Poland", :male} => 172.2,
  {"Poland", :female} => 159.4,
  {"United States", :male} => 175.3,
  {"United States", :female} => 161.3,
  {"Jamaica", :male} => 171.8,
  {"Jamaica", :female} => 160.8,
  {"Austria", :male} => 178.5,
  {"Austria", :female} => 166.9,
  {"Uruguay", :male} => 170,
  {"Uruguay", :female} => 158,
  {"Kenya", :male} => 169.6,
  {"Kenya", :female} => 158.2,
  {"England", :male} => 175.3,
  {"England", :female} => 161.9
}
data =
  heights
  |> Map.keys()
  |> Enum.map(fn {country, _sex} -> country end)
  |> Enum.uniq()
  |> Enum.map(fn country ->
    %{
      country: country,
      male: Map.get(heights, {country, :male}),
      female: Map.get(heights, {country, :female})
    }
  end)

Vl.new(width: 400, height: 300)
|> Vl.data_from_values(data)
|> Vl.mark(:point)
|> Vl.encode_field(:x, "male", type: :quantitative, title: "Male / [cm]", scale: [zero: false])
|> Vl.encode_field(:y, "female",
  type: :quantitative,
  title: "Female / [cm]",
  scale: [zero: false]
)
|> Vl.encode_field(:tooltip, "country", type: :nominal)

A selection of data from various people:

people = [
  %{name: "Usain Bolt", sex: :male, height: 195, born: 1986, country: "Jamaica"},
  %{name: "Hedy Lamarr", sex: :female, height: 170, born: 1914, country: "Austria"},
  %{name: "Bob Marley", sex: :male, height: 170, born: 1945, country: "Jamaica"},
  %{name: "Erwin Schrödinger", sex: :male, height: 188, born: 1887, country: "Austria"},
  %{name: "Tuomas Holopainen", sex: :male, height: 188, born: 1976, country: "Finland"},
  %{name: "Linus Torvalds", sex: :male, height: 177, born: 1969, country: "Finland"},
  %{name: "Idris Elba", sex: :male, height: 189, born: 1972, country: "England"},
  %{name: "Keira Knightley", sex: :female, height: 170, born: 1985, country: "England"},
  %{name: "Tilda Swinton", sex: :female, height: 180, born: 1960, country: "England"},
  %{name: "Kim Jong-un", sex: :male, height: 172, born: 1982, country: "North Korea"},
  %{name: "H C Andersen", sex: :male, height: 185, born: 1805, country: "Denmark"},
  %{name: "Aristotle", sex: :male, height: 172, born: -384, country: "Greece"},
  %{name: "Jacinda Ardern", sex: :female, height: 168, born: 1980, country: "New Zealand"}
]

Pick any two:

names =
  people
  |> Enum.map(fn person -> {person.name, person.name} end)
  |> Enum.sort()

Kino.Input.select("First person", names)
kino_name1 = Kino.Input.select("First person:", names)
kino_name2 = Kino.Input.select("Second person:", names)
Kino.Layout.grid([kino_name1, kino_name2])
persons =
  [Kino.Input.read(kino_name1), Kino.Input.read(kino_name2)]
  |> Enum.map(fn name ->
    Enum.find(people, fn person -> person.name == name end)
  end)

Decide on visual properties:

circles =
  persons
  |> Enum.map(fn person ->
    stroke =
      if person.sex == :female do
        "pink"
      else
        "rgb(127,127,255)"
      end

    case {person, Map.get(heights, {person.country, person.sex})} do
      {%{height: h}, avg} when h < avg -> {stroke, "red", h}
      {%{height: h}, _avg} -> {stroke, "blue", h}
      _ -> {stroke, "black", 130}
    end
  end)
{_color, _width} =
  edge =
  case {persons, circles} do
    {[person, person], _} ->
      {"rgb(0,127,0)", 8}

    {[%{sex: sex}, %{sex: sex}], [{_, _, h1}, {_, _, h2}]} ->
      {if sex == :male do
         "blue"
       else
         "pink"
       end, abs(h1 - h2)}

    {[%{}, %{}], [{_, fill, h1}, {_, fill, h2}]} ->
      {fill, abs(h1 - h2)}

    _ ->
      {"red", 5}
  end

Illustrate:

defmodule Illustrate do
  @height 600
  @width 800
  @sw 4

  defp produce_edge_code({x1, y1}, {x2, y2}, {color, width} = _edge) do
    """
    
    """
  end

  defp produce_person_code({x, y}, {stroke, fill, radius} = _person) do
    """
    
    """
  end

  def render(person_left, edge, person_right) do
    p_left = {1 * @width / 3, @height / 2}
    p_right = {2 * @width / 3, @height / 2}
    edge_code = produce_edge_code(p_left, p_right, edge)
    person_left_code = produce_person_code(p_left, person_left)
    person_right_code = produce_person_code(p_right, person_right)

    """
    
      #{edge_code}
      #{person_left_code}
      #{person_right_code}
    
    """
  end
end
Illustrate.render(Enum.at(circles, 0), edge, Enum.at(circles, 1))
|> Kino.Image.new(:svg)