Learn Elixir — Module and Method/Function
Let’s look this sample code below,
defmodule Coffees do
def hello_coffee do
"hi coffee lovers !"
end
end
at that code have 1 module named “Coffees” and 1 method inside “Coffees” ‘ module named “hello_coffee”
Module in elixir are collections of methods, but it’s different with class in OOP’s approach, module in elixir can’t instantiate as an object.
So, you can’t code like this
̶v̶a̶r̶1̶ ̶=̶ ̶n̶e̶w̶ ̶C̶o̶f̶f̶e̶e̶s̶
see, this diagram below.
method “hello_coffee” return a String.
in elixir, so many built in Modules, one of them is “String” module which is contain method/function “length” that compute total character of given string.
let’s look example below :
iex> String.length("hello")
5
In order to create our own modules in Elixir, we use the defmodule
macro. We use the def
macro to define functions in that module.
defmodule [module_name] do
def [method/function_name] do
//logic of method/function...
end
end
Method/function arguments
Inside a method/function, we can define argument/s,
syntax :
def method_name([argument1],[argument2],..) do
//logic of method/function...
end
for example :
defmodule Coffees do
def hello_coffee(name) do
"hi coffee lovers ! my name is " <> name
end
endiex=> Coffees.hello_coffee("akhmad")
"hi coffee lovers ! my name is akhmad"
Function Naming and Arity
In elixir, function can named by the combination of given name and arity (number of arguments). This means you can do things like this:
defmodule Greeter do # hello/0
def hello(), do: "Hello, anonymous person!"
# hello/1
def hello(name), do: "Hello, " <> name
# hello/2
def hello(name1, name2), do: "Hi, #{name1} and #{name2}"
endiex> Greeter.hello() "Hello, anonymous person!"
iex> Greeter.hello("Fred") "Hello, Fred"
iex> Greeter.hello("Fred", "Jane") "Hi, Fred and Jane"
We’ve listed the function names in comments above. The first implementation takes no arguments, so it is known as hello/0
; the second takes one argument so it is known as hello/1
, and so on. Unlike function overloads in some other languages, these are thought of as different functions from each other. (Pattern matching, described just a moment ago, applies only when multiple definitions are provided for function definitions with the same number of arguments.)
Default arguments
functions in Elixir also support default arguments
syntax :
def func_name(arg1, arg2 [\\ [default_argument_value]]) do
//logic is here
end
argument “\\ [default_argument_value”
example :
defmodule Concat do
def join(a, b, separator \\ " ") do
a <> separator <> b
end
end
iex=> Concat.join("Hello", "world")
"Hello world"
iex=> Concat.join("Hello", "world", "_")
"Hello_world"
Any expression is allowed to serve as a default value, but it won’t be evaluated during the function definition. Every time the function is invoked and any of its default values have to be used, the expression for that default value will be evaluated:
defmodule DefaultTest do
def dowork(x \\ "hello") do
x
end
endiex> DefaultTest.dowork
"hello"
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
"hello"
Anonymous Function
Just as the name implies, an anonymous function has no name. As we saw in the Enum
lesson, these are frequently passed to other functions. To define an anonymous function in Elixir we need the fn
and end
keywords. Within these we can define any number of parameters and function bodies separated by ->
.
Let’s look at a basic example:
iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5
The & Shorthand
Using anonymous functions is such a common practice in Elixir there is shorthand for doing so:
iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5
As you probably already guessed, in the shorthand version our parameters are available to us as &1
, &2
, &3
, and so on.
Public and Private Method/Function
Inside a module, we can define “public” functions with def
macro and “private” functions with defp
macro. A function defined with def
macro can be invoked from other modules while a private function can only be invoked locally.
defmodule Math do
def public_sum(a, b) do
private_sum(a, b)
end
defp private_sum(a, b) do
a + b
end
end
iex=> Math.public_sum(1, 2)
3
iex=> Math.private_sum(1, 2)
** (UndefinedFunctionError)
Functions and Pattern Matching
Behind the scenes, functions are pattern-matching the arguments that they’re called with.
Say we needed a function to accept a map but we’re only interested in using a particular key. We can pattern-match the argument on the presence of that key like this:
defmodule Greeter do
def hello(%{name: person_name}) do
IO.puts “Hello, “ <> person_name
end
end
These are the results we’ll get when we call Greeter.hello/1
with the fred
map:
iex=> fred = %{name: "Fred", age: "95", favorite_color: "Taupe"}
iex=> Greeter.hello(fred)
"Hello, Fred"
It finds that there is a key that corresponds to name
in the incoming map. We have a match! And as a result of this successful match, the value of the :name
key in the map on the right (i.e. the fred
map) is bound to the variable on the left (person_name
).
What happens when we call the function with a map that doesn’t contain the :name
key?
iex=> someone = %{age: "65", favorite_color: "Green"}
iex=> Greeter.hello(someone)
** (FunctionClauseError) no function clause matching in Greeter.hello/1 The following arguments were given to Greeter.hello/1: # 1 %{age: "65", favorite_color: "Green"} iex:12: Greeter.hello/1
The reason for this behavior is that Elixir pattern-matches the arguments that a function is called with against the arity the function is defined with.
Guards
Function declarations also support guards and multiple clauses. If a function has several clauses, Elixir will try each clause until it finds one that matches.
In the following example we have two functions with the same signature, we rely on guards to determine which to use based on the argument’s type:
defmodule Greeter do
def hello(names) when is_list(names) do
names
|> Enum.join(", ")
|> hello
end
def hello(name) when is_binary(name) do
phrase() <> name
end
defp phrase, do: "Hello, "
endiex> Greeter.hello "Akhmad"
"Hello, Akhmad"iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"
is_
prefix (is_foo
)
Type checks and other boolean checks that are allowed in guard clauses are named with an is_
prefix.
Examples: Integer.is_even/1
, Kernel.is_list/1
These functions and macros follow the Erlang convention of an is_
prefix, instead of a trailing question mark, precisely to indicate that they are allowed in guard clauses.
Note that type checks that are not valid in guard clauses do not follow this convention. Examples: Keyword.keyword?/1
, Regex.regex?/1
Function/method with Trailing question mark (foo?
)
Functions that return a boolean are named with a trailing question mark.
Examples: Keyword.keyword?/1
, Mix.debug?/0
, String.contains?/2
However, functions that return booleans and are valid in guards follow another convention, described next.