In Elixir, function overloading can be achieved through both pattern matching and defining functions with different numbers of arguments (arities).
In this post, we are going to take a closer look at the function overloading via pattern matching. Please see post #6 for an explanation of function overloading based on functions with different arities.
Function overloading can be achieved through pattern matching, where multiple function clauses with different patterns can exist within the same function definition. When a function is called, Elixir matches the arguments against the patterns in the function clauses and executes the body of the first matching clause.
Key points about function overloading with pattern matching in Elixir:
Multiple Clauses: A function can have multiple clauses with different patterns.
Pattern Matching: Elixir matches the function arguments against the patterns in the clauses to determine which clause to execute.
Order Matters: Function clauses are evaluated from top to bottom, so the order of clauses is important.
Flexible Behavior: Function overloading allows for different behavior based on the input arguments.
Example of function overloading with pattern matching in Elixir:
defmodule Math do
def add(0, b), do: b
def add(a, 0), do: a
def add(a, b), do: a + b
end
IO.puts(Math.add(2, 3)) # Output: 5
IO.puts(Math.add(0, 5)) # Output: 5
IO.puts(Math.add(7, 0)) # Output: 7
In the above example, the add/2
function is overloaded with three clauses. The first clause handles the case where the first argument is 0, the second clause handles the case where the second argument is 0, and the third clause handles the general case of adding two numbers.
Additional Examples:
- Pattern Matching via the use of Guards:
defmodule Math do
def add(a, b) when is_integer(a) and is_integer(b), do: a + b
def add(a, b) when is_float(a) and is_float(b), do: a + b
end
IO.puts Math.add(2, 3) # Output: 5 (integer addition)
IO.puts Math.add(2.5, 3.5) # Output: 6.0 (float addition)
defmodule StringOps do
def concat(a, b) when is_binary(a) and is_binary(b), do: a <> b
def concat(a, b) when is_list(a) and is_list(b), do: List.flatten([a, b])
end
IO.puts StringOps.concat("Hello, ", "world!") # Output: "Hello, world!"
IO.puts StringOps.concat(['H', 'e', 'l', 'l', 'o'], [' ', 'w', 'o', 'r', 'l', 'd', '!']) # Output: "Hello world!"
defmodule Shape do
def area({:rectangle, width, height}), do: width * height
def area({:circle, radius}), do: 3.14 * radius * radius
end
Function overloading in Elixir is not a mysterious concept—it relies on familiar principles you already understand! Once you grasp its workings, you'll appreciate why functions are processed in a top-to-bottom manner.
When you define a function with multiple clauses, Elixir consolidates them into a single function represented by a case statement. For instance, a basic is_weekday/1
function can be structured as follows:
defmodule Examples.Patterns.Weekday do
@weekdays ~w(Monday Tuesday Wednesday Thursday Friday)
def is_weekday(day) when day in @weekdays do
true
end
def is_weekday(_), do: false
end
IO.inspect Examples.Patterns.Weekday.is_weekday("Monday") # Output: true
IO.inspect Examples.Patterns.Weekday.is_weekday("Saturday") # Output: false
In this example, the Examples.Patterns.Weekday
module defines a function is_weekday/1
that checks if a given day is a weekday based on a predefined list of weekdays. The function pattern matches the input day against the list of weekdays and returns true
if it is a weekday, and false
otherwise.
Here is the version of the Examples.Patterns.Weekday
module with a single is_weekday/1
function represented by a case statement to determine if a given day is a weekday:
defmodule Examples.Patterns.Weekday do
@weekdays ~w(Monday Tuesday Wednesday Thursday Friday)
def is_weekday(day) do
case day do
day when day in @weekdays -> true
_ -> false
end
end
end
IO.inspect Examples.Patterns.Weekday.is_weekday("Monday") # Output: true
IO.inspect Examples.Patterns.Weekday.is_weekday("Saturday") # Output: false
The Elixir compiler seamlessly handles this process, highlighting the importance of the order of function definitions from top to bottom. This feature also showcases the effective utilization of guards within case statements, as Elixir seamlessly converts them into case statements during compilation.
Function overloading with pattern matching in Elixir allows for concise and expressive code by defining different behaviors for different input patterns within the same function. This approach simplifies code maintenance and readability by encapsulating related logic in a single function definition.