Functions and Modules
Defining functions
In elixir we can define functions with fn
keyword
add = fn a, b -> a + b end
This is an anonymous function
In Javascript
we write this like
const adder = (a, b) => a + b
in python
this is known as a lambda function
adder = lambda a, b: a + b
Now lets execute our add function
add.(10, 11)
to invoke an anonymous function you need to use a dot(.
)
this is used to identify anon function invoking from other function definitions
Closures
Like in JS we can access surrounding scope
# Create an anonymous function that takes a greeting and returns another function
make_greeting = fn greeting ->
# This inner function captures the `greeting` variable, creating a closure
fn name ->
"#{greeting}, #{name}!"
end
end
# Create a closure that says "Hello"
hello = make_greeting.("Hello")
# Create another closure that says "Goodbye"
goodbye = make_greeting.("Goodbye")
hello.("WaveZync ๐")
goodbye.("World")
In JS it looks like this
// Create a function that takes a greeting and returns another function
const makeGreeting = (greeting) => (name) => `${greeting}, ${name}!`;
// Create a closure that says "Hello"
const hello = makeGreeting("Hello");
// Create another closure that says "Goodbye"
const goodbye = makeGreeting("Goodbye");
Clauses and Guards
In the following function we have two parameters
in each clause we are doing pattern matching and same time using a guard with when
as a fallback we have the last catch all clause to handle any other param
can_drink = fn
age, "USA" when age >= 21 -> "You're allowed to drink in the USA."
_age, "USA" -> "You're not allowed to drink in the USA."
age, "Germany" when age >= 16 -> "You're allowed to drink in Germany."
_age, "Germany" -> "You're not allowed to drink in Germany."
age, "Japan" when age >= 20 -> "You're allowed to drink in Japan."
_age, "Japan" -> "You're not allowed to drink in Japan."
_, country -> "Drinking age information for #{country} is not available."
end
IO.puts can_drink.(18, "USA")
IO.puts can_drink.(21, "USA")
IO.puts can_drink.(17, "Germany")
IO.puts can_drink.(15, "Germany")
IO.puts can_drink.(20, "Japan")
IO.puts can_drink.(18, "Japan")
IO.puts can_drink.(18, "France")
Capture Operator
We used func_name/arity
convention(like put_in/2
), actually it can be used with capture operator
add = &+/2
add.(1,3)
Remember in Elixir +
is also a function ๐ (Defined in Kernel
module as Kernel.+/2
)
This becomese useful later with data transformation
Now we can capture any function as a variable also
We can also use a shorthand notation for anon functions
add_1 = &(&1 + &2)
add_2 = fn a, b -> a + b end
add_1.(1, 5)
add_2.(1, 5)
This is used practically to simplify many data transformations an example is listed below
names = ["alice", nil, "bob", "", "carol"]
result =
names
|> Enum.reject(&is_nil/1) # Remove nil values
|> Enum.reject(&(&1 == "")) # Remove empty strings
|> Enum.map(&String.upcase/1) # Convert each name to uppercase
|> Enum.map(&("Name: #{&1}")) # Format each name with a prefix
IO.inspect(result)
Modules
Modules allow to organize functions. In other languages this is like a class(but there is a difference)
Simply think of this as a group of functions
defmodule MathUtils do
@moduledoc """
A simple module with basic mathematical utility functions.
"""
@doc """
Adds two numbers.
"""
def add(a, b) when is_number(a) and is_number(b) do
a + b
end
@doc """
Multiplies two numbers.
"""
def multiply(a, b) when is_number(a) and is_number(b) do
a * b
end
@doc """
Checks if a number is even.
"""
def even?(n) when is_number(n) do
rem(n, 2) == 0
end
@doc """
Calculates the square of a number.
"""
def square(n) when is_number(n) do
n * n
end
@doc """
Describes a number as positive, negative, or zero.
"""
def describe_number(n) when is_number(n) and n > 0 do
"The number is positive."
end
def describe_number(n) when is_number(n) and n < 0 do
"The number is negative."
end
def describe_number(0) do
"The number is zero."
end
def describe_number(_non_number) do
"This is not a valid number."
end
end
# Testing the module
IO.puts MathUtils.describe_number(5) # Outputs: The number is positive.
IO.puts MathUtils.describe_number(-3) # Outputs: The number is negative.
IO.puts MathUtils.describe_number(0) # Outputs: The number is zero.
IO.puts MathUtils.describe_number("hello") # Outputs: This is not a valid number.
In elixir as a convention we are using ? to indicate a function result will be a boolean
In above example we can see describe_number/1
appear multiple times with guards
This pattern allows us to move logic into function signature and do pattern matching to get a result Its a common pattern in elixir to pattern match on function signature itself
Also compared to a if
statement, when you have patterns in the function header compiler does optimizations
Private functions
We can define private functions with defp
.
In elixir its common to have lot of small private functions
defmodule Greeter do
# Public function that uses a private helper function
def greet(name) do
message = format_greeting(name)
"Greeting message: #{message}"
end
# Private function to format the greeting
defp format_greeting(name) do
"Hello, #{name}!"
end
end
Greeter.greet("Kasun")
Default Parameters
We can pass default params with \\
defmodule Greeting do
def greet(name, greeting \\ "Hello") do
"#{greeting}, #{name}!"
end
end
IO.puts Greeting.greet("Alice") # Outputs: Hello, Alice!
IO.puts Greeting.greet("Alice", "Hi") # Outputs: Hi, Alice!
IO.puts Greeting.greet("Alice", "Welcome") # Outputs
If a function with default values has multiple clauses, we need to createe a function head (definition without body) to declarae defaults
defmodule Describe do
# Function head declaring defaults
def details(name, age \\ nil, title \\ "Unknown")
# Clause where age is not provided
def details(name, nil, title) do
"#{title} #{name}"
end
# Clause where all parameters are provided
def details(name, age, title) do
"#{title} #{name}, age #{age}"
end
end
IO.puts Describe.details("Alice", 30, "Dr.") # Outputs: Dr. Alice, age 30
IO.puts Describe.details("Alice", 30) # Outputs: Unknown Alice, age 30
IO.puts Describe.details("Alice") # Outputs: Unknown Alice