Want to see the full-length video right now for free?
 
  The original code may look something like this,
defmodule DrawShapes do
  def areas(shapes) do
    for shape <- shapes do
      area(shape)
    end
  end
  def area(shape) do
    case shape.shape_type do
      :circle ->
        (shape.radius * :math.pi) |> :math.pow(2)
      :square ->
        shape.side |> :math.pow(2)
    end
  end
end
shapes = [
  %{shape_type: :circle, radius: 3},
  %{shape_type: :square, side: 4}
]
DrawShapes.areas(shapes)
And we could extract higher level data types,
defmodule DrawShapes do
  def areas(shapes) do
    for shape <- shapes do
      area(shape)
    end
  end
  def area(shape) do
    module = shape.__struct__
    apply(module, :area, [shape])
  end
end
defmodule Circle do
  defstruct radius: nil
  def new(radius), do: %Circle{radius: radius}
  def area(circle) do
    (circle.radius * :math.pi) |> :math.pow(2)
  end
end
defmodule Square do
  defstruct side: nil
  def new(side), do: %Square{side: side}
  def area(square) do
    square.side |> :math.pow(2)
  end
end
shapes = [Circle.new(3), Square.new(4)]
DrawShapes.areas(shapes)
We can also define a protocol for how an abstraction should behave. This is a powerful tool in Elixir and it allows us to get polymorphic behavior.
defmodule Circle do
  defstruct radius: nil
  def new(radius), do: %Circle{radius: radius}
end
defmodule Square do
  defstruct side: nil
  def new(side), do: %Square{side: side}
end
defprotocol Shape do
  def area(shape)
end
defimpl Shape, for: Circle do
  def area(circle) do
    (circle.radius * :math.pi) |> :math.pow(2)
  end
end
defimpl Shape, for: Square do
  def area(square) do
    square.side * square.side
  end
end
defmodule DrawShapes do
  def areas(shapes) do
    for shape <- shapes do
      area(shape)
    end
  end
  def area(shape) do
    Shape.area(shape)
  end
end
shapes = [Circle.new(2), Square.new(2)]
DrawShapes.areas(shapes)