Manuel Albarran

A random notes about Elixir

Polymorphism (Part 5) - Behaviour through Macros with overrides

18 Feb 2017 » elixir, polymorphism, behaviour, macros

Using defoverridable for allow optimization of circularity in Circle and Square

defmodule Polygon do
  @moduledoc "Specification of a surface."
  @type t :: module

  @doc "Calculates the area of a surface"
  @callback area(t) :: number
  @doc "Calculates the perimeter of a surface"
  @callback perimeter(t) :: number

  defmacro __using__(_) do
    quote do
      @behaviour Polygon

      def circularity(polygon) do
        area = polygon |> polygon.__struct__.area
        perimeter = polygon |> polygon.__struct__.perimeter 

        (4 * :math.pi * area) / (perimeter * perimeter)
      end

      defoverridable circularity: 1
    end
  end
end

defmodule Circle do
  defstruct [:r]
  use Polygon

  @doc "Calculates the area of a circle"
  def area(%Circle{r: r}), do: :math.pi * r * r
  
  @doc "Calculates the perimeter of a circle"
  def perimeter(%Circle{r: r}), do: 2 * :math.pi * r

  def circularity(%Circle{}), do: 1.0
end

defmodule Square do
  defstruct [:a]
  use Polygon
  
  @doc "Calculates the area of a square"
  def area(%Square{a: a}), do: a * a
  
  @doc "Calculates the perimeter of a square"
  def perimeter(%Square{a: a}), do: 4 * a
  
  def circularity(%Square{a: a}), do: :math.pi / 4
end

defmodule Rectangle do
  defstruct [:w, :h]
  use Polygon

  @doc "Calculates the area of a polygon"
  def area(%Rectangle{w: w, h: h}), do: w * h

  @doc "Calculates the perimeter of a polygon"
  def perimeter(%Rectangle{w: w, h: h}), do: 2 * w + 2 * h
end

%Circle{r: 10} |> Circle.area
#> 304.0592653589793
%Circle{r: 10} |> Circle.circularity
#> 1.0 # A Perfect Circle

%Square{a: 10} |> Square.area
#> 100
%Square{a: 10} |> Square.circularity
#> 0.7853980633974483

%Rectangle{w: 10, h: 5} |> Rectangle.area
#> 50
%Rectangle{w: 10, h: 5} |> Rectangle.circularity
#> 0.6981317007977318

[%Circle{r: 10}, %Square{a: 10}, %Rectangle{w: 10, h: 5} ] |> Enum.each(fn polygon ->
  IO.puts polygon |> polygon.__struct__.area
  IO.puts polygon |> polygon.__struct__.perimeter
  IO.puts polygon |> polygon.__struct__.circularity
end)
#> 314.1592653589793
#> 62.83185307179586
#> 1.0
#> 100
#> 40
#> 0.7853981633974483
#> 50
#> 30
#> 0.6981317007977318