Manuel Albarran

A random notes about Elixir

Polymorphism (Part 6) - Protocols implementation with fallbacks

18 Feb 2017 » elixir, polymorphism, protocol

We could implement fallbacks implementent deimpl {{Protocol}}, for: Any

defimpl Geometry, for: Any do
  def circularity(polygon) do
    area = polygon |> Polygon.area
    perimeter = polygon |> Polygon.perimeter 

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

Usign derive for Rectangle

We can use @derive directive for use Any implementation in particular structs, like here in Rectanglez

defprotocol Polygon do
  @doc "Calculates the area of a polygon"
  def area(polygon)
  @doc "Calculates the perimeter of a polygon"
  def perimeter(polygon)
end

defprotocol Geometry do
  def circularity(polygon)
end

defimpl Geometry, for: Any do
  def circularity(polygon) do
    area = polygon |> Polygon.area
    perimeter = polygon |> Polygon.perimeter 

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

defmodule Circle do
  defstruct [:r]
end

defimpl Polygon, for: Circle do
  def area(%Circle{r: r}), do: :math.pi * r * r
  def perimeter(%Circle{r: r}), do: 2 * :math.pi * r
end

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

defmodule Square do
  defstruct [:a]
end

defimpl Geometry, for: Square do
  def circularity(%Square{}), do: :math.pi / 4
end

defimpl Polygon, for: Square do
  def area(%Square{a: a}), do: a * a
  def perimeter(%Square{a: a}), do: 4 * a
end

defmodule Rectangle do
  @derive [Geometry]
  defstruct [:w, :h]
end

defimpl Polygon, for: Rectangle do
  def area(%Rectangle{w: w, h: h}), do: w * h
  def perimeter(%Rectangle{w: w, h: h}), do: 2 * w + 2 * h
end

%Circle{r: 10} |> Polygon.area
#> 314.1592653589793
%Circle{r: 10} |> Geometry.circularity
#> 1.0

%Square{a: 10} |> Polygon.area
#> 100
%Square{a: 10} |> Geometry.circularity
#> 0.7853981633974483

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

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

Usign @fallback_to_any in Geometry

Or we can use @fallback_to_any directive in Geometry for use Any implementation in all structs that don’t override the protocol

defprotocol Polygon do
  @doc "Calculates the area of a polygon"
  def area(polygon)
  @doc "Calculates the perimeter of a polygon"
  def perimeter(polygon)
end

defprotocol Geometry do
  @fallback_to_any true
  def circularity(polygon)
end

defimpl Geometry, for: Any do
  def circularity(polygon) do
    area = polygon |> Polygon.area
    perimeter = polygon |> Polygon.perimeter 

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

defmodule Circle do
  defstruct [:r]
end

defimpl Polygon, for: Circle do
  def area(%Circle{r: r}), do: :math.pi * r * r
  def perimeter(%Circle{r: r}), do: 2 * :math.pi * r
end

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

defmodule Square do
  defstruct [:a]
end

defimpl Geometry, for: Square do
  def circularity(%Square{}), do: :math.pi / 4
end

defimpl Polygon, for: Square do
  def area(%Square{a: a}), do: a * a
  def perimeter(%Square{a: a}), do: 4 * a
end

defmodule Rectangle do
  defstruct [:w, :h]
end

defimpl Polygon, for: Rectangle do
  def area(%Rectangle{w: w, h: h}), do: w * h
  def perimeter(%Rectangle{w: w, h: h}), do: 2 * w + 2 * h
end

%Circle{r: 10} |> Polygon.area
#> 314.1592653589793
%Circle{r: 10} |> Geometry.circularity
#> 1.0

%Square{a: 10} |> Polygon.area
#> 100
%Square{a: 10} |> Geometry.circularity
#> 0.7853981633974483

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

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