This video is only a short sample, but you can access the full version and all our other great content by subscribing.

Video

Notes

Using modules to create data abstractions

  • "Hide" the underlying data structure (Map, Struct, List). Note: data is transparent, meaning it isn't really hidden.
  • Define functions in the module that take the data structure as its argument
  • Usually, you'll see query functions return some other type
  • Modifier functions return the same type, but modified (e.g. update)

An Example

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)

Using protocols

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)

Further Reading