Elixir Basics: Part 2 - Guards, Functions and Modules
In Part 1, you defined some functions in the interactive elixir shell (iex). These functions are stored in elixir memory only for that iex session - once you quit the session, or when your repl reboots, those functions defined by you will no longer be stored.
A better way to store functions is in 'modules'. Modules store functions and allow them to be compiled, called on, and exported to other modules. You may think of a module as a collection of functions, often related to a common topic or purpose. e.g. the 'math' module has functions such as
pi and more.
Let's create our first module! In your files pane, create a new file 'uno.ex' and type:
defmodule Uno do @moduledoc""" This is your first module in elixir. Created on <<today's date>> """ @vsn 0.1 def inverse(num) do 1/num end end
We just created a module named 'Uno' and defined a function called 'inverse' inside the module.
- '@moduledoc' is used to capture documentation for the module.
- This documentation is for the entire module, not just one function in the module.
- There is a separate spec ('@doc') to capture function-specific documentation.
- When you call help ('h' in iex) on the module, the documentation captured inside '@moduledoc' appears, along with other information.
- Comments should be written using hastags '#', not in the @moduledoc.
You can also use the '@vsn' spec to specify the version of the module.
To access the function, we must compile the code. In your shell, type:
Now start the iex session by typing
Your module was defined using the uppercase 'U'. Elixir recognizes 'Uno', not 'uno'.
Try calling the inverse function:
To call a function, you must provide the module name ('Uno' here) and the function name ('inverse') both.
We need a 'guard' to bypass this ArithmeticError.
Guards in elixir work somewhat like input validation statements in other programming languages. The purpose is to perform the function only if certain conditions on the input are met.
Guard statements begin with
when and are followed by the conditional clause.
When you pass
0 as the input to the inverse function, you'll get a division-by-0 error, or in Elixir, an ArithmeticError.
You can use a Guard clause to tell Elixir that the function must be performed only for non-zero numbers. Add a guard to your inverse function
defmodule Uno do @moduledoc""" This is your first module in elixir. Created on <<today's date>> """ @vsn 0.2 def inverse(num) when (num>0 or num<0) do 1/num end end
The clause beginning with 'when' tells the program to process the following lines of code only if num is more than or less than 0. Let's try
inverse(0) again. We will need to compile the module again.
The FunctionClause Error occcurs because we've told the program to not perform the 'inverse' function if the input is 0, but have not told it what to do instead.
(Think of this like an if-else statement, where you didn't add anything in the else statement)
Let's return 'the inverse of 0 is not defined' when 0 is passed as an input to the inverse function. We'll also add some function documentation.
defmodule Uno do @moduledoc""" This is your first module in elixir. Created on <<today's date>> """ @vsn 0.2 @doc""" For a non-zero number, the 'inverse' function returns 1 divided by the number itself. When the input is 0, it will print the statement "the inverse of 0 is not defined". """ def inverse(num) when (num>0 or num<0) do 1/num end def inverse(num) do IO.puts("The inverse of 0 does not exist") end end
Let's compile again and review:
Notice the 'variable is unused' warning when the module is compiled. In the second function clause, 'num' is passed as an argument, but not used in the function result. You can avoid this warning by adding an underscore (_) to the variable name i.e.
_num in place of
Create a module 'Skywalker' inside a new elixir file 'skywalker.ex'. Add module documentation saying that it contains the functions anakin.
Define a function 'anakin' inside the Skywalker module. The function should accept the number of midichlorians as an input, compare this to Anakin's midichlorian count (20000) and return "You have more midichlorians than Anakin Skywalker" if the input is more than 20000, otherwise return "Anakin has the highest midichlorian count in the galazy". The function should perform this comparison only on non-negative inputs.
Try passing strings such as "hello", "lightsaber" as arguments in the anakin function. Can you modify the code to ensure only numbers are accepted as valid inputs? (Hint: Refer the original Elixir documentation here )
Solutions will be revealed in the Part 3 Tutorial.
Solutions to Part 1 :
first = [45, "jump", 66]
second = ["ogre", 32, 89]
third = first ++ second
fourth = third -- ["ogre"]
inverse = fn a -> 1/a end
twenty = inverse.(20)
fiftyone = inverse.(51)
seventyone = twenty + fiftyone
trunc(seventyone) == round(seventyone)
Other useful resources on this topic:
What is Guard in Elixir