Elixir Typespecs

Welcome to a tutorial on Typespecs in Elixir.

As you already know, Elixir is a dynamically typed language, hence, all types in Elixir are inferred by the runtime. However, it comes with type specs, which are a notation used for declaring custom data types and declaring typed function signatures (or specifications).

 

Function Specifications(specs)

Elixir provides some basic types, such as integers or PIDs, and also complex types, by default. For instance, the round function, which rounds a float to its nearest integer, takes a number as an argument (an integer or a float) and returns an integer. In the related documentation, the round typed signature is written as:

round(number) :: integer

The above code description implies that the function on the left takes as an argument what is specified in parenthesis and returns what is on the right of: that is Integer. The function specs are written with the @spec directive, and placed right before the function definition. The round function can be written as shown below.

@spec round(number) :: integer
def round(number), do: # Function implementation
...

The Typespecs support complex types as well, e.g. if you want to return a list of integers, then you can use [Integer]

 

Custom Types

Elixir provides a lot of useful inbuilt types. This is convenient to define custom types when appropriate. You can do this when defining modules through the @type directive. Check out the example below.

defmodule FunnyCalculator do
   @type number_with_joke :: {number, String.t}

   @spec add(number, number) :: number_with_joke
   def add(x, y), do: {x + y, "You need a calculator to do that?"}

   @spec multiply(number, number) :: number_with_joke
   def multiply(x, y), do: {x * y, "It is like addition on steroids."}
end

{result, comment} = FunnyCalculator.add(10, 20)
IO.puts(result)
IO.puts(comment)

The output is:

30
You need a calculator to do that?

A calculator is needed to do this.

Note that custom types defined through @type are exported and available outside the module they are defined in. but, if you want to keep a custom type private, you can use the @typep directive instead of @type