Emulating “duck typing” in Elixir

Get the same convenience in a functional language

billperegoy
im-becoming-functional
4 min readMay 9, 2017

--

What is Duck Typing?

If you’re familiar with dynamic object-oriented languages like Ruby, it’s likely you’ve heard the term duck typing. It’s an odd term, but in short it relates to a technique where you define a number of classes and subclasses with a common API. Each of these classes responds to the same public methods so you can call the same methods on any of these classes without knowing which particular class you are interfacing with.

Using duck typing allows us to write really expressive programs with very simple code. Instead of using complex conditionals to pick a different action for each subclass, we can depend upon the object oriented inheritance features built into the language.

As I begin to get more involved with functional languages like Elixir, I longed for a pattern that would allow me to create code that was as simple and elegant, but using a functional approach. In this post, I’ll both work through an example of duck typing in Ruby and explore two methods of creating similar functionality in a functional fashion with Elixir.

Duck Typing in Ruby

One of the classic examples used to demonstrate duck typing in Ruby is a generic animal class that can be overridden to provide different behavior for different types of animals. We do this by providing a generic function with a non-functional implementation for the method that will be differ between subclasses. Here’s an example base class.

class Animal
attr_reader :name, :age
def initialize(name:, age:)
@name= name
@age= age
end
def speak
raise "speak is not implemented"
end
end

Here we have a generic animal class. All animals will have a name and age, but we want to have a unique implementation of the speak method for each type of animal. Note that if we try to invoke the speak method on the base class, we will throw an exception.

Given this starting point, we can now derive classes for different animal types from this base class.

class Dog < Animal
def speak
"Ruff"
end
end
class Duck < Animal
def speak
"Quack"
end
end
class Cat < Animal
def speak
"Meow"
end
end

Note that we only redefine the behavior that is different from the base class (in this case, the speak method). Other methods that are common to all subclasses (name and age in this case) do not need to be redefined.

This is classic object oriented behavior and allows us to write very expressive code without the need for an ugly conditional to decide type-based behavior.

Moving to a Functional World

Moving to a functional language like Elixir forces us to think about this problem in a different manner. In functional programming we have no ability bind methods to state. In fact, we have no mutable state at all and only pure, stateless functions to work with.

We will start by creating an Elixir Map that represents the data. We will store the same name and age information along with an atom that represents the animal type. So we will end up with these sorts of definitions.

dog = %{type: :dog, name: "Fido", age: 4}
duck = %{type: :duck, name: "Mr. Quackers", age: 2}
cat = %{type: :cat, name: "Fuzzy", age: 8}
animal = %{type: :animal, name: "Mysterious", age: 5}

Given this, we can easily extract the name and age attributes from any animal type using simple functions.

def name(animal) do
animal.name
end
def age(animal) do
animal.age
end

These functions can be used for any type of animal, but for the speak function, it gets a little messy.

  def speak(animal) do
case animal.type do
:dog ->
"Ruff"
:duck ->
"Quack"
:cat ->
"Meow"
_ ->
raise "speak is not implemented"
end
end

While this function does its job, it certainly isn’t pretty and doesn’t use the powerful functional capabilities of Elixir. We will next use the pattern matching features of Elixir to get rid of this conditional and create a much cleaner version of this function.

Using Pattern Matching in Elixir Functions

There are a number of ways to implement pattern matching in Elixir. Here, we will be using pattern matching on function parameters. With this sort of pattern matching, we can define different functions with the same name which respond to different patterns on the input Map. Using this approach, we will create a separate function clauses that match on different animal types.

def speak(%{type: :dog}) do
"Ruff"
end
def speak(%{type: :duck}) do
"Quack"
end
def speak(%{type: :cat}) do
"Meow"
end

def speak(_) do
raise "speak is not implemented"
end

With this, we now have an implementation that avoids the ugliness of that large (and potentially growing) conditional and reduces the problem to a series of pure, simple functions. I find this to be much more readable than the conditional alternative. This gives us the same sort of duck typing behavior we had in the Ruby version, but in a purely functional way.

Summary

Moving from an object oriented mindset to a more functional way of thinking can be a challenge. This can be particularly difficult with Elixir. Given that the syntax borrows so much from Ruby, it’s very easy to get stuck in a Ruby (and object oriented) way of thinking. It’s important to break down a problem and think in terms of creating pure functions, leveraging the power of the built-in Elxir data types and using Elixir pattern matching to create clean and simple solutions.

--

--

billperegoy
im-becoming-functional

Polyglot programmer exploring the possibilities of functional programming.