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

## 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 ->
:square ->
shape.side |> :math.pow(2)
end
end
end

shapes = [
%{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

def area(circle) do
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

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
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)
``````