Introduction to Elixir
Updated: 03 May 2024
Notes from the Elixir Programming Introduction YouTube Video
Installation and Setup
Installation
- Follow the installation instructions as per the Elixir Docs for your operating system
- Install the Elixir Language Server for VSCode (
JakeBecker.elixir-ls
) - Install the Elixirt Formatter for VSCode (
saratravi.elixir-formatter
)
You can text the installation by opening the Elixir repl using iex
(interactive elixir) in your terminal
Running Code
To start writing some code, we just need to create a file wih an exs
extension
You can then run it using:
In general, we use
exs
for code that we will run using the interpreter andex
for code we will compile
Mix
Mix is a tool for working with Elixir code. We can use the mix
command to create and manage Elixir projects
Programming in Elixir
Creating a Project
We can create an example project with some code in it by using mix new
, we can create a project like so:
This should show the following output:
For our sake,. we’ll be working in the lib/example.ex
file which defines the module for our application
A module is effectively a mainspace which is within the do...end
block. We also have a hello
function in our module. The generated file can be seen below:
Interacting with a Module
We can compile the project using:
Then, we can interact with this code interactively using iex
Thereafter we will find ourself with the module loaded into the interactive session, we can interact with the code that we loaded via the module like:
Next, you can run the function using mix
In the case when we use mix
the result of the function will not be output since it is nit printed using IO.puts
Running a Project
Something important to know - code outside of the module definition is executed when the code is compiled, not during runtime
We can define an entrypoint at our application configuration level - this is done in the mix.exs
file:
Next, we need to provide a start method in our module that will be called when the app is run. We can clear out our Example
module and will just have the following:
In the above, we use the _
prefix to denote that we’re not using those parameters - if we remove these we will get warnings when using mix
We can run the above code using either mix
or mix run
:
The above line with Supervisor.start_link
isn’t really doing anything as yet - but it is needed to satisfy the requirement of Elixir that the app returns a supervision tree
Dependencies
Dependencies in Elixir can be installed using hex
which is Elixir’s package manager. We can set this up by using:
We can then look for packages on Hex Website. For the sake of example we’re going to install the uuid
package. We do this by adding it to the mix.exs
file:
Therafter, run the following command to install the dependencies:
We can then use the package we installed in our code:
Syntax
Defining Variable Bindings
You can also implement constants at the module level by using @
as follows:
Atoms and Strings
Atoms are kind of like an alternative to a string. These values have the same name and value and are constant - these cannot be randomly defined by users
We prefer to use atoms for static values since these have better performance since certain things like comparing values can be done based on memory location for atoms instead of value for strings
We can also have spaces and special characters in atoms provided we enclose it in quotes
Conditional Statements
Conditions use the following syntax:
We can do equality checking using ===
or ==
which is less strict (a bit like javascript)
Case Statements
In the above, we use the _
as a default case
Strings
IO.puts
prints a string and adds a newline at the end. Strings can contain special characters and expressions as well as as various other things like unicode character codes
Numbers
Elixir is dynamically typed so it doesn’t care too much about how we work with these kinds of data types
The float type is 64 bit in Elixir and there is no double type
We can also format and print numbers using :io.format
:
Formatting the above also shows us that we don’t have very high precision using floats as normally using floating points values
There are also numerous other methods for working with numbers contained in the Float
namespace. For example the ceil
method:
The same goes for integers, their methods are located in the Integer
namespace
Compound Types
Compound types are types that consist of many other values
For these compound types we can’t use IO.puts
to print them out since they’re not directly stringable - we can instead use IO.inspect
which will print them out in some way that makes sense for the data type as in the following example:
Dates and Times
We can create dates and times using their respective constructors:
In the above code the new
functions return a result, if we want to unwrap this we can add a !
in our function call:
Unwrapping the values lets us use them directly if we want to, we can do this along with different functions for working with dates, e.g. converting it to a string:
Tuples
Tuples allow us to store multiple values in a single variable. Tuples have a fixed number of elements and they can be different data types. Tuples use {}
We can also create a new tuple to which we append new values using Tuple.append
We can also do math on tuples, for example as seen below:
We can get individual properties out of a tuple using the elem
method with the index:
Or we can descructure the individual values:
Lists
Lists are used when we have a list of elements but we don’t know how many elements we will have. Lists use []
We can also use the Enum.each
method to iterate over these values as we can see above
Maps
Maps are key-value pairs. Maps use %{}
Maps are expecially useful since we will also get autocomplete for the fields that are in the map.
Structs
Structs are used for defining types that have got defined structures
Structs can be defined within a module using the defstruct
keyword. This is the struct that the module operates with:
And we can create an instance of the struct using the %Name{}
syntax
We can access properties of the struct using dot notation:
Random
We can get a random int using the following:
Whenever we use the :name
syntax for accessing a namespace it means we are referring to some Erlang based code
Piping
We can get user input using the IO.gets
method:
In the above example we also use function piping to trim and parse the input string
Pattern Matching
The above leads to the result being either a value or an error, we can use pattern matching to interpret this value:
If we want to ignore the partial error case, we can use an _
:
Combining the above, we can create a small guessing game:
List Comprehension
List comprehension is used for doing some operation for each item in a list, the syntax is as follows:
We can also assign the result of the do
to a new array
We can also combine a comprehension with a condition:
The condition in the above example is
n > 50
but this can be anything that evaluates to a boolean
Appending to Lists
If we want to append to a list we can use the ++
operator:
Prepending to a List
We can use the |
operator to prepend to a list using the following syntax:
Function Arity
The arity of a function refers to how many parameters the fuction takes. In elixir functions use the /
t ndicate the arity. This is because we may have multiple functions with the same name in a module that each have a different arity. For example, there are two versions of String.split
which take one and two parameters. We refer to these as String.split/1
and String.split/2
respectively:
Passing Functions as Parameters
We can pass anonymous functions to other functions:
We can pass fuctions to other functions. For example, equivalent to the above:
The
&
operator converts a function to an anonymous function. This requires that we also specify the arity of a function so that we can pass the correct instance as a callback
Defining Functions
We can define functions using the def name do ... end
syntax:
And we can use this:
Another small example is a function for printing out a list of numbers: