~21m5:18:02Functional Programming with Elixir – Full Course
May 10, 2023
Read: ~21m · You save: 297 min
Functional Programming with Elixir – Complete Course
Learn functional programming with Elixir! Master recursion, pattern matching, the actor model, and build powerful applications.
This course, developed by Octolium, offers a comprehensive introduction to the Elixir programming language and the functional programming paradigm. Participants will explore key concepts such as immutability, recursion, and pattern matching, as well as the actor model for building concurrent and fault-tolerant systems. The course is designed for developers with basic programming experience who wish to master powerful tools for creating scalable applications.
Introduction to Elixir
The course, developed by Octolium, covers the fundamentals of Elixir and functional programming. It delves into recursion, pattern matching, and the actor model. The course also touches upon data types, control flow, and advanced topics like umbrella projects and list operations. Upon completion, participants will be equipped to build their own Elixir applications and confidently apply functional programming concepts.
The course is designed for individuals with a foundational understanding of programming in languages such as JavaScript, Python, Java, or C++. Programming experience is required, but expertise is not.
The course will cover the following topics:
- Functional programming fundamentals.
- Elixir basics, including its type system.
- Project creation using Mix.
- Developing a statistics library as a final project.
Elixir is a functional programming language. WhatsApp and Discord applications run on the same virtual machine. Discord uses Elixir, while WhatsApp uses Erlang. Erlang is also a functional programming language.
Elixir and Erlang Fundamentals
Elixir is built on top of Erlang, and both languages compile and run on the Erlang Virtual Machine, also known as BEAM. This virtual machine is analogous to the Java Virtual Machine, which runs languages like Java, Kotlin, Clojure, and Scala. Elixir compiles and runs on the BEAM virtual machine.
Key advantages of Elixir include its functional nature and its default support for immutability. Data immutability provides high scalability.
Parallelism and Actors in Elixir
Elixir lacks the ability for threads or other objects to modify values, which ensures state preservation and enhances system scalability. One of Elixir's key features is its support for fault tolerance. If a node or an actor (process) fails, it can be automatically restarted while preserving its state. This allows for the creation of distributed and fault-tolerant systems.
Functional programming in Elixir means that the entire program consists of various functions. A function takes input data (an argument), transforms it, and returns a result. This process, while seemingly simple, involves creating pure functions that always return the same result for the same input data and have no side effects.
Functional Programming
Functional programming lacks classes and objects. All data types are immutable, which means working with constants throughout the program.
Immutable Data Types
Since data is not mutable, the state remains the same. This allows data to be copied and distributed much more easily. This distribution of data makes it possible to create scalable systems using functional programming.
Elixir does not have for loops. This is due to the immutability of data. In a traditional for loop, the iteration variable is constantly changing (e.g., incrementing). In Elixir, where data is immutable, this approach is not possible. To perform iterations in Elixir, recursion is used, which will be discussed in detail in the next section.
Recursion
Recursion is a process where the same task is performed repeatedly, calling itself.
The complexity of recursion lies in its application. The concept of "wonders of the world" can be given as an example. Although there is a generally accepted list, the personal perception of a wonder can vary. For outstanding individuals like Warren Buffett and Charlie Munger, one of the wonders of the world is compounding.
Comparison with Habits and Systems
The perception of the world is individual; Isaac Newton considered gravity the greatest wonder of the world. The author believes that recursion combined with mutation is one of the greatest wonders.
Human habits, repeated day after day, can be viewed as a state of recursion. Changes in habits occur gradually, through small mutations, which allows us to speak of a state of recursive mutation.
The Earth completes a full rotation around its axis in 24 hours, returning to its starting point, which is an example of recursion. The Earth's orbital motion around the Sun takes 12 months, but every four years an extra day is added (a leap year), which can be interpreted as a recursive mutation.
The expansion of the Universe, which began with the Big Bang about 13.6 billion years ago, can also be explained using a recursive function. The BigBang function is called without arguments, initiating the expansion process.
Installing Elixir
To install Elixir, navigate to the elixirlang.org website and select the "Install" section. Windows users should download the installer for Windows. The installation process involves sequentially clicking "Next" buttons and concludes by clicking "Finish." macOS users can install Elixir using the Homebrew package manager. To do this, open the terminal and execute the command brew install elixir.
After installing Elixir, it is recommended to verify the version by opening the terminal (or PowerShell on Windows) and entering the command elixir -v. The current version of Elixir is 1.14.1. The materials presented in the course will be compatible with future versions.
Next, configure the Elixir plugin for Visual Studio Code. To do this, open the extensions in the editor.
Working with Elixir Files in VS Code
To start working with Elixir in VS Code, you need to install the Elixir LS extension. This extension has over 339,000 downloads. After installing the extension, the first time you open an Elixir file in VS Code, you will see a notification about building the PLT (Project Loading Time). This process may take some time. Once the PLT build is complete, you can start working with Elixir files.
The Match Operator in Elixir
In mathematics, equality means that the left side is equivalent to the right side. For example, in the expression a = 1, the left side (a) is equal to the right side (1). Mathematics does not define assigning the value of the right side to the left. If a = 1, then 1 = a should also be true.
In Elixir, the = operator is not an assignment operator but a match operator. It matches the right side to the left side. This mechanism, known as pattern matching, is a powerful tool in Elixir. Every expression with the = operator should be interpreted as an attempt to match the right side to the left side.
You can use Elixir's interactive shell (REPL) to demonstrate this operator.
The IEX Interactive Shell
To launch the IEX interactive shell, type the command iex in your terminal. To clear the terminal screen, use the clear command.
In IEX, you can assign values to variables. For example, to assign the value 1 to the variable a:
iex> a = 1
1
You can then check if values are equal using the == operator. If a is equal to 1, the expression a == 1 will return true.
iex> a == 1
true
If the variable a already holds the value 1, the expression 1 == a will also return true, as both operands have the same value.
iex> 1 == a
true
The == operator performs an equality check, not an assignment.
IEX supports pattern matching. For instance, when working with lists, denoted by square brackets [], you can match variables to list elements. If a list contains two elements, and both variables on the left side have the same name, for example a, the value of the first list element will be assigned to the variable a, and the value of the second element will also be assigned to the variable a. If the values of the list elements are identical, the match will be successful.
Example of pattern matching for a list:
iex> [a, a] = [1, 1]
[1, 1]
In this case, since both list elements are equal to 1, the variable a will receive the value 1. The expression will return the original list [1, 1].
To exit IEX, press Ctrl + C twice.
Introduction and Setup
Pattern matching in Elixir allows you to compare the left-hand side and right-hand side of an expression. If they are identical, the match succeeds. For example, the expression a = 1 assigns the value 1 to the variable a. When you try to match a with 1, the operation will be successful, and the value of a will be 1.
When matching a and a with 1 and 2 respectively, an error occurs. This is because the value 1 is assigned to the variable a, and then the second part attempts to match a (which is already 1) with the value 2. Since 1 is not equal to 2, the match fails, and a "no match of the right hand side" error is generated.
If you change the right-hand side by replacing the second a with a new variable b, for example, a and a with 1 and 2, the match will be successful. In this case, 1 is assigned to the variable a, and 2 is assigned to the variable b. You can verify this by accessing the variables a and b, which will contain their respective values.
Pattern matching is a powerful tool in Elixir that is used everywhere.
For a more convenient way to work with Elixir, in addition to the terminal, you can use Livebook. Livebook is an equivalent to Jupyter Notebook for Elixir. To install Livebook, go to the website livebook.dev, select your operating system (macOS or Windows), and download the installer. After installation, you can run the program.
In Livebook, you can create new notebooks by clicking "New notebook". To execute code, use the "evaluate" button. For example, the expression a, a = 1, 1 will be executed successfully. You can also execute more complex expressions, such as if a == 1 do 1 == a end.
For this course, example notebooks are available on GitHub at github.com/octallium/functional-programming-with-Elixir. In the repository, within the notebooks folder, you will find three notebooks. These notebooks can be opened in Livebook by selecting the corresponding file through the icon on the left and specifying the path to the repository. The notebooks contain text descriptions and code blocks for practice.
Continuing your exploration of pattern matching, you can return to the terminal and launch the interactive IEX shell by typing the command iex. In the terminal, you can execute the match a, a = 1, 2, which, as expected, will result in a "no match of the right hand side" error.
Elixir Fundamentals: Immutability and Pattern Matching
In Elixir, the variable a is bound to the value 1. Attempting to match the value 2 with the variable a results in a match error because a is already bound to the value 1.
This occurs because all data types in Elixir are immutable. It is not possible to change the value of an existing variable after it has been initialized. Elixir's data immutability contributes to system scalability.
When using the match operator (=), if a variable is on the left side of the operator, Elixir binds the new value to that variable. For example, if a is bound to the value 1, and we write a = 2, then a will now be bound to the value 2.
To prevent this behavior and ensure strict value correspondence, the pin operator (^) is used. For instance, ^a = 3 will raise a match error if a is bound to a value other than 3. This is because the pin operator checks if the existing value of the variable matches the specified value, rather than assigning a new value.
If the match operator is used in reverse, for example, 3 = a, and a is bound to the value 2, a match error will also occur because 3 is not equal to 2.
Elixir's data immutability makes it easy to copy data between different processes without concerns about other resources modifying that data.
Processes and Isolation in Elixir
Code in Elixir runs within entities called actors. An actor can be thought of as an isolated computational unit. It receives messages, processes them, and returns a response. An actor operates within processes. Millions of processes can exist concurrently.
Data in Elixir is immutable. This allows for the creation of many copies of data and their distribution among different actors. These actors can run on a local machine or across a global cluster of servers, enabling horizontal load scaling.
Processes in Elixir are not operating system processes. They can be thought of as virtual threads. Each process has a unique identifier called a PID (Process ID). Interaction between processes occurs through message passing.
Since data is immutable, many copies can be created and assigned to different processors. Each actor receives its own copy of the data, eliminating issues with concurrent state modification. Everything runs in isolation.
Each process has its own stack and heap allocation. Unlike compiled languages like C or C++, where memory is allocated on the stack or heap, in Elixir, each process gets its own stack and heap allocation. This speeds up garbage collection, as only one process's memory needs to be cleaned up, preserving application responsiveness.
Each actor has its own mailbox. All messages sent to an independent process are collected in this mailbox and processed sequentially on a first-in, first-out (FIFO) basis.
Creating processes is very cheap, requiring less than 3KB of memory. This allows for the creation of millions of processes concurrently. Interaction always happens through message passing.
A process ID (PID) can be checked using the built-in self function. Parentheses around the function call are optional. For example, self and self() are equivalent. It is currently common practice to use parentheses when writing code in an editor, but it is not mandatory in the terminal. The PID of the current process running the IEX interactive shell looks like 0.107.0.
Creating and Compiling Elixir Files
In Elixir, code is organized into modules. The defmodule keyword is used to define a module, followed by the module name and a do...end block. By convention, the module name should match the filename.
The def keyword is used to define functions within a module. Functions can accept arguments, which are specified in parentheses after the function name. If a function does not take any arguments, the parentheses can be omitted. The function's code block is also enclosed in do...end.
Strings in Elixir are defined using double quotes. Single quotes are reserved for atoms.
Creating Script Files
Elixir supports two types of file extensions: .exs for script files and .ex for compiled files. Script files, with the .exs extension, are intended for direct code execution without prior compilation. They are often used for development tasks such as seeding a database or for testing.
To run a script file, the command elixir <filename.exs> is used. If the code within the script involves a function call, you must explicitly specify the module and function to execute. For example, to call the world function from the hello module, the syntax Hello.world() is used.
Compiling Files
Elixir is a compiled language. Files with the .ex extension are intended for compilation. To compile a file, the command elixirc <filename.ex> is used. This command compiles the source code into bytecode, which can then be executed.
Elixir Tooling Overview
Elixir code is compiled into .beam files, which are executed on the Beam virtual machine. The mix tool is typically used to compile and run Elixir files.
A compiled hello.exs file can be executed in the interactive shell, IEX. When attempting to recompile a module that is already in memory, a redefinition warning will appear, which can be ignored. After executing the file in IEX, the output hello Elixir is displayed.
Functions in Elixir can be called by specifying the module and function name. Parentheses around arguments are optional. For example, calling hello.world without arguments returns hello Elixir and the atom ok. Atoms are one of Elixir's data types.
Functions can accept parameters. When using string interpolation to include a parameter within a string, the syntax #{parameter_name} is used. To recompile a modified module in IEX, the r command is used. For instance, r Hello recompiles the Hello module. After recompilation, a function can be called with a parameter, such as hello.world("Octalium"), which will return hello Octalium.
All data types in Elixir are immutable, meaning they are constants.
# The Impact of Artificial Intelligence on the Job Market
Artificial Intelligence (AI) is rapidly transforming various sectors, and its impact on the job market is a subject of intense discussion. While some fear widespread job displacement, others foresee the creation of new roles and enhanced productivity.
## Potential Job Displacement
One of the primary concerns surrounding AI is its potential to automate tasks currently performed by humans. This is particularly true for jobs involving repetitive or data-intensive processes. Examples include:
* **Manufacturing:** AI-powered robots can perform assembly line tasks with greater speed and precision.
* **Customer Service:** Chatbots and virtual assistants are increasingly handling customer inquiries.
* **Data Entry and Analysis:** AI algorithms can process and analyze vast datasets much faster than humans.
This automation could lead to a significant reduction in demand for certain types of labor, potentially causing unemployment in affected industries.
## Creation of New Roles
Conversely, AI is also expected to create new job opportunities. These roles will likely be in areas related to the development, deployment, and maintenance of AI systems. Some emerging roles include:
* **AI Engineers and Developers:** Professionals who design, build, and train AI models.
* **Data Scientists:** Experts who collect, clean, and interpret data for AI applications.
* **AI Ethicists:** Individuals who ensure AI systems are developed and used responsibly and fairly.
* **AI Trainers and Annotators:** People who provide the data and feedback necessary to train AI models.
Furthermore, AI can augment human capabilities, leading to increased efficiency and the ability to tackle more complex problems. This could result in existing jobs evolving rather than disappearing.
## The Need for Reskilling and Upskilling
To navigate this evolving landscape, a strong emphasis on reskilling and upskilling the workforce is crucial. Individuals will need to acquire new skills, particularly in areas related to technology, data literacy, and critical thinking, to remain competitive. Educational institutions and employers will play a vital role in providing accessible training programs.
## Conclusion
The impact of AI on the job market is multifaceted. While challenges related to job displacement exist, the potential for new job creation and enhanced productivity is also significant. Proactive adaptation through education and training will be key to harnessing the benefits of AI while mitigating its risks.
Atoms in Elixir
An atom in Elixir is a literal whose name is the same as its value. Atoms start with a colon (:), followed by a name. For example, :nike is an atom.
If the atom's name contains spaces, it is enclosed in double quotes. For example, :"the nike" is a valid atom.
Conceptually, an atom is like a symbol or a logo that uniquely identifies a specific value. In pseudocode, this can be represented as a variable nike with the value nike. In Elixir, this is expressed as the atom :nike.
Atoms are widely used in Elixir, especially for pattern matching. They are often used to denote states or error messages. For example, the atom :error can be used to represent an error.
Example of creating an atom in the interactive shell IEX:
iex> :nike
:nike
iex> :"the nike"
:"the nike"
iex> :error
:error
Atoms are often returned in tuples to indicate the result of an operation, for example, when an error occurs.
## Tuples in Elixir
Tuples in Elixir are immutable data structures consisting of a fixed number of elements. They are often used for returning values from functions, particularly to indicate the success or failure of an operation.
A common pattern for using tuples involves returning a two- or three-element tuple. The first element often signifies the outcome of the operation: for instance, the atom `:error` to denote an error, or the atom `:ok` to denote success. Subsequent elements contain additional information, such as the error reason or the returned value.
An example of using a tuple to indicate an error:
```elixir
{:error, "file not found"}
In this example, the first element, :error, signals an error, and the second element, the string "file not found", describes the reason.
Pattern matching is the primary way to work with tuples in Elixir. You can match the structure of a tuple on the left side of an expression against a tuple on the right side. During this process, elements of the tuple can be bound to variables.
Consider an example of pattern matching for an error tuple:
tuple = {:error, "file not found"}
{:error, reason} = tuple
In this code, the variable reason will be bound to the value "file not found". Checking the value of the reason variable will confirm this:
reason
Result:
"file not found"
Similarly, tuples are used to indicate successful operations. For example, when working with web applications, a successful response might be represented as follows:
{:ok, "status 200 ok"}
Here, the atom :ok indicates success, and the string "status 200 ok" contains the status message.
An example of pattern matching for a successful tuple:
success_tuple = {:ok, "status 200 ok"}
{:ok, message} = success_tuple
In this case, the variable message will receive the value "status 200 ok". Outputting the value of message will show:
message
Result:
"status 200 ok"
This pattern, where the first element of a tuple indicates the operation's status (error or success) and subsequent elements contain associated data, is widely used in Elixir.
Strings and Lists in Elixir
In Elixir, strings are represented using double quotes. For example, "octalium" is a valid string. Using single quotes instead of double quotes defines a character list, which is different from a string.
The Elixir interactive shell (IEX) provides functions for checking data types. The is/1 function can be used to get information about a variable. When calling is("octalium") in IEX, the result will be an output indicating that the variable has the value "octalium" and the data type "binary string".
Strings in Elixir are stored as collections of bytes. This means that the "binary string" data type actually represents a sequence of bytes in memory. A string in Elixir is a binary representation encoded in UTF-8. The size of the string "octalium" is 9 bytes. In memory, strings start with double angle brackets and end with them.
Working with Bytes and Protocols
Strings in Elixir are represented as collections of bytes. Each character in a string corresponds to an integer representation. For example, the number 79 represents 'O', and 99 represents 'c'. Several protocols are implemented for the string type, which is a more advanced topic and not covered in this guide.
Strings can be viewed as sequences of bytes, and with pattern matching, individual code points can be extracted from a string. For instance, you can match a string by specifying the first character ('o') and collecting the rest of the string into a variable rest. As a result, the rest variable will contain all characters following 'o'.
iex> "octalium"
"octalium"
iex> ?o
79
iex> ?c
99
iex> is_binary("octalium")
true
iex> message = "hello" <> name
"hello octalium"
iex> "hello " <> name = message
"hello octalium"
iex> name
"octalium"
When pattern matching strings, you can use either the regular representation or the raw byte representation. The raw string representation is used with double angle brackets << >>.
iex> <<head::binary, rest::binary>> = "octalium"
"octalium"
iex> head
"o"
iex> rest
"ctalium"
When working with strings, you can use the concatenation operator «>> to join strings.
iex> message = "hello" <> name
"hello octalium"
You can also use pattern matching to extract parts of a string.
iex> "hello " <> name = message
"hello octalium"
iex> name
"octalium"
When pattern matching the raw string representation, you can specify the size of the collected bytes.
iex> <<head::binary-size(2), rest::binary>> = "onc"
"onc"
iex> head
"on"
Introduction to Elixir
## Introduction to Elixir The course by Octolium is dedicated to the fundamentals of Elixir and functional programming. It covers recursion, pattern matching, the actor model, data types, flow control, and advanced topics such as mixed projects and list operations. The goal of the course is to teach students to build their own Elixir applications and confidently apply functional programming concepts. The course is designed for individuals with basic programming knowledge in any language (JavaScript, Python, Java, C++). The course will cover the basics of functional programming and Elixir, Elixir's type system, project creation using Mix, and the development of a final project – a statistics library. Elixir is a functional programming language. Examples of applications using Elixir or Erlang (on which Elixir is based) include Discord (on Elixir) and WhatsApp (on Erlang).
- The course is dedicated to the fundamentals of Elixir and functional programming.
- The course is created by Octolium.
- Course topics include: recursion, pattern matching, actor model, data types, flow control, mixed projects, list operations.
- Course goal: to teach how to build Elixir applications and apply functional programming concepts.
- Target audience: individuals with basic programming knowledge.
- Prerequisites: basic programming experience in any language (JavaScript, Python, Java, C++).
- Course plan: basics of functional programming, basics of Elixir, Elixir type system, project creation using Mix, development of a statistics library as a final project.
- Elixir is a functional programming language.
- Discord runs on Elixir.
- WhatsApp runs on Erlang.
- Erlang is also a functional programming language.
Elixir and Erlang Fundamentals
## Elixir and Erlang Fundamentals Elixir and Erlang run on the BEAM virtual machine, similar to how Java, Kotlin, Clojure, and Scala run on the JVM. Elixir is a functional programming language and defaults to immutability, which enables high scalability.
- Alexa is built on Erlang.
- Elixir and Erlang are compiled languages.
- Elixir and Erlang run on the BEAM virtual machine (also known as the Erlang VM).
- The BEAM virtual machine is similar to the Java Virtual Machine (JVM).
- Many languages, such as Java, Kotlin, Clojure, and Scala, compile and run on the JVM.
- Elixir compiles and runs on the BEAM virtual machine.
- Elixir is a functional programming language.
- Elixir defaults to immutability.
- Immutability enables high scalability.
Parallelism and Actors in Elixir
## Parallelism and Actors in Elixir In Elixir, state is preserved because threads or other objects cannot directly modify values, ensuring scalability. Elixir supports fault tolerance: when a node or actor fails, it automatically restarts, preserving its state, which enables the creation of distributed and fault-tolerant systems. Functional programming in Elixir means that a program is composed of functions. A function takes input (an argument), transforms it, and returns a result. Creating pure functions involves many aspects.
- Elixir systems are scalable because threads or other objects cannot directly modify values, preserving state.
- Elixir supports fault tolerance.
- When a node or actor fails in Elixir, it automatically restarts, preserving its state.
- Elixir enables the creation of distributed and fault-tolerant systems.
- Functional programming means a program is composed of functions.
- A function takes input, transforms it, and returns a result.
- There are aspects involved in creating pure functions.
Functional Programming
## Functional Programming Functional programming lacks classes and objects. All data types are immutable, which means working with constants throughout the program.
- Functional programming has no classes or objects.
- All data types are immutable.
- In functional programming, operations are performed with constants.
Immutable Data Types
## Immutable Data Types Immutable data types, or constants, mean that the state of the data remains unchanged. This allows for easy copying and distribution of data, which contributes to the creation of scalable systems using functional programming. Functional programming and Elixir lack traditional `for` loops, as they imply variable mutation (e.g., incrementing a counter `i`). Instead, recursive calls are used for iterations, which will be discussed in detail in the next video.
- Immutable data types mean that the state of the data remains unchanged.
- Data immutability allows for easy copying and distribution.
- The use of immutable data contributes to the creation of scalable systems.
- Functional programming and Elixir lack
forloops. forloops imply data mutation (e.g., changing the variableiini++).- In Elixir, recursive calls are used for iterations instead of
forloops. - Recursion will be discussed in detail in the next video.
Recursion
## Recursion Recursion is the repeated execution of the same task, where the task calls itself. Warren Buffett and Charlie Munger consider **compounding** (compound interest) one of the wonders of the world.
- Recursion is the repeated execution of the same task.
- In a state of recursion, the task calls itself.
- Warren Buffett and Charlie Munger consider compounding one of the wonders of the world.
Comparison with Habits and Systems
## Comparison with Habits and Systems This section draws an analogy between the concept of recursive mutation and everyday phenomena such as human habits and the movement of celestial bodies. The author suggests that people's repetitive behavior can be viewed as a form of recursion, and changes in habits as mutation. Similarly, the Earth's rotation and its orbital motion around the Sun, including leap years, are also presented as examples of recursive processes with elements of mutation. Finally, the expansion of the universe after the Big Bang is compared to a recursive function demonstrating growth from an initial state.
- Isaac Newton would consider gravity one of the greatest wonders of the world.
- The perception of wonder is subjective: for the author, it is recursion, or more precisely, recursive mutation.
- Human habits, repeated day after day, can be seen as a state of recursion.
- Changes in habits, occurring gradually, can be seen as mutation.
- The combination of habit repetition and their gradual changes can be called recursive mutation.
- The Earth rotates on its axis in approximately 24 hours, which can be considered a state of recursion.
- The Earth orbits the Sun in approximately 12 months.
- Every four years, the Earth experiences a leap year, adding one day, which can be considered a mutation in its orbital motion.
- The universe is in a state of expansion.
- Approximately 13.6 billion years ago, the Big Bang occurred, after which the Earth, stars, Sun, and galaxies emerged.
- The expansion of the universe can be explained using a recursively written function.
- The Big Bang can be represented as a function that takes no arguments.
Installing Elixir
## Installing Elixir To install Elixir, go to the website `elixirlang.org` and click "Install". **For Windows:** Download the installer and follow the instructions: "Next", "Next", "Finish". **For macOS:** Use Homebrew. Open your terminal and enter the command `brew install elixir`. **Verifying Installation:** Open your terminal (or PowerShell for Windows) and enter `elixir -v`. The current version is 1.14.1. The functionality presented in this video will also work in future versions. **Setting up Visual Studio Code:** Install the Elixir plugin through the "Extensions" section in VS Code.
- To install Elixir, you need to go to the elixirlang.org website.
- The elixirlang.org website has an "Install" section.
- For Windows users, an installer is available that is installed by clicking "Next", "Next", "Finish".
- Mac users can install Elixir using Homebrew by entering the command
brew install elixirin the terminal. - You can verify the Elixir installation by entering the command
elixir -vin the terminal. - The current version of Elixir shown in the video is 1.14.1.
- The material presented in the video will work in future versions of Elixir.
- To set up Elixir in Visual Studio Code, you need to install the corresponding plugin through the "Extensions" section.
Working with Elixir Files in VS Code
## Working with Elixir Files in VS Code To start working with Elixir in VS Code, you need to install the Elixir LS extension. After installation, the first time you open an Elixir file, VS Code will start building the PLT (Program Loading Table), which may take some time. Once the PLT build is complete, you can start working with Elixir files. It's important to understand that object-oriented programming concepts, such as variables, differ in the functional world of Elixir. For example, the assignment `a = 1` in Elixir does not mean creating a variable `a` assigned the value `1`.
- To work with Elixir in VS Code, you need to install the Elixir LS extension.
- The Elixir LS extension has 339,000 downloads.
- After installing the Elixir LS extension, the PLT (Program Loading Table) build process starts the first time you open an Elixir file in VS Code.
- The PLT build takes some time.
- After the PLT build is complete, you can work with Elixir files in VS Code.
- Concepts from the object-oriented world, such as variables, differ in the functional world of Elixir.
- Example: the assignment
a = 1in Elixir does not mean creating a variableawith the assigned value1in the traditional sense.
The Match Operator in Elixir
## The Match Operator in Elixir In Elixir, the `=` operator is a match operator, not an assignment operator as in traditional algebra. This means it checks if the right-hand side matches the left-hand side, rather than assigning a value to a variable. This is a fundamental aspect of pattern matching in Elixir, a powerful concept that allows you to check if data structures match. You can demonstrate this operator's behavior in Elixir's interactive shell (REPL).
- In basic algebra, the expression "a = 1" means the left side is equal to the right side.
- In basic algebra, there is no concept of assigning the right side to the left side.
- In basic algebra, if the left side equals the right side, then the right side also equals the left side.
- In Elixir, the
=operator is a match operator, not an assignment operator. - The match operator in Elixir checks if the right-hand side matches the left-hand side.
- Pattern matching is a powerful concept in Elixir.
- In Elixir, the
=operator always means matching the right-hand side to the left-hand side. - Elixir comes with an interactive shell (REPL) for using the language.
The IEX Interactive Shell
## The IEX Interactive Shell ### Entering and Exiting IEX To enter the IEX interactive shell, type `iex`. To exit, press `Ctrl+C` twice. ### Clearing the Screen To clear the terminal screen in IEX, use the `clear` command. ### Assignment and Comparison in IEX In IEX, the `=` operator is used for assignment, and `==` is used for comparison. Example: * `a = 1`: Assigns the value `1` to the variable `a`. * `1 == a`: Compares `1` with the variable `a`. The result will be `true` if `a` equals `1`. * `1 = a`: In this case, `1` and `a` have the same value, and the comparison operator returns `true`. ### Pattern Matching IEX supports pattern matching. This means you can compare the structure of data on the right side of an expression with the structure on the left. Example with a list: * `[a, a] = [1, 1]`: Here, the list `[1, 1]` is matched against the pattern `[a, a]`. The value `1` is assigned to the variable `a`. Since both variables on the left side are named `a`, and both have the value `1` on the right side, the result will be `true`. It's important to understand that `==` is a comparison operator, not an assignment operator. It checks if the right side matches the left side.
- The command
iexis used to enter the IEX interactive shell. - Press
Ctrl+Ctwice to exit IEX. - The
clearcommand clears the terminal in IEX. - In IEX, the variable
acan be equal to1. - The comparison
1 == areturnstrueifaequals1. - The comparison
1 = areturnstrueif1andahave the same value. - The
=operator in IEX is used for assignment, while==is for comparison. - IEX supports pattern matching.
- Square brackets
[]denote a list in IEX. - Pattern matching example:
[a, a] = [1, 1]returnstrue. - In the example
[a, a] = [1, 1], the value1is assigned to the variablea. - In IEX, you can use the variable
my_nameand assign it the valueoctallium. - The comparison
octallium == my_namewill returntrue.
Introduction and Setup
This section introduces pattern matching in Elixir, demonstrating its use in IEx and Livebook. It explains how pattern matching works by comparing the left and right sides of an expression and how errors occur when they don't match. Livebook is also presented as an alternative to IEx for interactive Elixir development, with instructions on installation and usage. Links to a GitHub repository with Livebook notebook examples are provided.
- Pattern matching in Elixir requires the left and right sides of an expression to be equal.
- Example:
a = 1successfully assigns the value 1 to the variablea. - Example:
a = 1, a = 2results in an error because the value ofa(1) does not match the expected value (2). - Example:
a = 1, b = 2successfully binds 1 toaand 2 tob. - Livebook is a Jupyter Notebook-like tool for Elixir.
- Livebook can be installed from livebook.dev by selecting the appropriate operating system.
- New notebooks can be created in Livebook, and Elixir code can be executed.
- Example code in Livebook:
a = 1, a = 1executes successfully. - Example code in Livebook:
a = 1, 1 = aexecutes successfully. - The GitHub repository for the tutorial series is located at: octallium/functional-programming-with-elixir.
- The repository contains example Livebook notebooks for use.
- Livebook notebooks can be opened in the Livebook application by selecting the notebook file path.
Elixir Fundamentals: Immutability and Pattern Matching
## Elixir Fundamentals: Immutability and Pattern Matching ### Data Immutability in Elixir In Elixir, all data types are immutable. This means that once a value is assigned to a variable, it cannot be changed. Attempting to assign a new value to an existing variable will result in a match error. ### The Match Operator and Its Features The `=` operator in Elixir is used for pattern matching. If a variable is on the left side of the `=` operator, Elixir interprets this as binding a new value to the variable. For example, `a = 1` binds the value `1` to the variable `a`. ### The Pin Operator (`^`) To prevent a variable's value from being changed when using the match operator, the pin operator (`^`) is used. For example, `^a = 2` will result in a match error if `a` already has a different value, because the `^` operator checks if the existing value of the variable matches the pattern, rather than assigning a new value. ### Reasons for Immutability Data immutability in Elixir is used to ensure scalability. Immutable data can be easily copied between different processes without concerns about other resources modifying them.
- The value of variable
ais bound to the value1. - Attempting to match the value
2with variablea, which is already bound to the value1, results in a match error. - All data types in Elixir are immutable.
- Data immutability in Elixir is used to ensure scalability.
- The
=operator in Elixir is used for pattern matching. - If a variable is on the left side of the
=operator, Elixir binds a new value to that variable. - The pin operator
^is used to check if the existing value of a variable matches a pattern, rather than assigning a new value. - Using
^a = 3whenais equal to2results in a match error. - Binding the value
2to variableais possible becauseais on the left side of the=operator. "3 = a" results in an error because 3 does not equal 2. - Data immutability allows data to be easily copied between processes without concerns.
Processes and Isolation in Elixir
## Processes and Isolation in Elixir ### Actors and the Actor Model Code in Elixir runs inside entities called actors. Actors can be thought of as isolated "boxes" that receive messages (data or instructions), process them, and return a response. They are isolated units of computation. ### Processes in Elixir Actors run inside processes. Millions of processes can exist concurrently in the system. These processes are not operating system processes but rather virtual threads. Each process has a unique identifier (PID). ### Isolation and Immutability Data in Elixir is immutable. This allows for millions of copies of data to be distributed across different actors and processes, including across different servers in a cluster. Since each actor receives its own copy of the data, changes to state by one actor do not affect others, ensuring isolation. ### Memory and Garbage Collection Each process has its own stack and heap allocation. This simplifies garbage collection, as it is performed for each process independently, making the application responsive. ### Actor Mailbox Each actor has its own mailbox where all incoming messages are collected. Messages are processed in the order they arrive (FIFO - First-In, First-Out). ### Cost of Processes Processes in Elixir are very cheap to create, requiring less than 3KB of memory. This allows for millions of processes to be created concurrently. ### Inter-Process Communication Interaction between processes is achieved through message passing. ### Process Identifier (PID) Each process has a unique identifier called a PID. In IEX (Elixir's interactive shell), you can get the PID of the current process using the `self` function. For example, an IEX process PID might look like `0.107.0`. ### Function Call Syntax Parentheses are not always mandatory when calling functions in Elixir. For example, `self` can be written as `self()` or simply `self`. It's common practice to use parentheses in code editors and omit them in the terminal.
- Code in Elixir runs inside actors.
- Actors are isolated units of computation that receive, process messages, and return responses.
- Actors run inside processes.
- Millions of processes can exist concurrently in an Elixir system.
- Elixir processes are virtual threads, not operating system processes.
- Each process has a unique identifier (PID).
- Data in Elixir is immutable.
- Immutability allows for many copies of data to be distributed across actors and processes.
- Process isolation prevents state changes in one actor from affecting others.
- Each process has its own stack and heap allocation.
- Separate garbage collection for each process enhances application responsiveness.
- Each actor has a mailbox for incoming messages.
- Messages are processed in FIFO order (First-In, First-Out).
- Creating processes requires less than 3KB of memory, making them very cheap.
- Communication between processes occurs via message passing.
- The
selffunction in IEX returns the PID of the current process. - Parentheses are not always mandatory for function calls in Elixir (e.g.,
selforself()).
Creating and Compiling Elixir Files
## Creating and Compiling Elixir Files ### Script Files and Compiled Files In Elixir, there are two types of files: script files with the `.exs` extension and compiled files with the `.ex` extension. `.exs` files are intended for running scripts, for example, for populating a database or for testing, and are not intended for production. `.ex` files are used for creating compiled projects, which will be covered when using the `mix` tool. ### Modules and Functions All code in Elixir resides within modules. A module is defined using the `defmodule` keyword, followed by the module name and a `do...end` block. By convention, the filename should match the module name. Within a module, functions are defined using the `def` keyword. Functions that take no arguments can have empty parentheses or omit them entirely. Strings in Elixir are enclosed in double quotes, while atoms are enclosed in single quotes. ### Running and Compiling Scripts Elixir scripts with the `.exs` extension can be run directly using the command `elixir <filename.exs>`. To execute code within a script, the corresponding function must be called. To compile and run a script file simultaneously, use the command `elixirc <filename.exs>`, which first compiles the file and then executes it.
- Elixir has two file extensions:
.exsfor scripts and.exfor compiled files. .exsfiles are used for development, such as populating a database or testing, and are not intended for production.- All code in Elixir must reside within modules, defined using
defmodule. - The
defkeyword is used to define functions. - Functions that take no arguments can have empty parentheses or omit them.
- Strings in Elixir are enclosed in double quotes, and atoms are enclosed in single quotes.
- By convention, the filename should match the module name.
.exsscripts can be run with the commandelixir <filename.exs>.- To call a function from a module, use the syntax
<module_name>.<function_name>(). - The command
elixirc <filename.exs>is used to compile and run an.exsscript.
Overview of Elixir Tools
The video demonstrates compiling and running Elixir code, as well as interacting with it through the interactive shell IEX. It shows how to compile a `.exs` file into a `.beam` bytecode file, which is executed on the Beam virtual machine. It explains that the `mix` tool is typically used for project management. The compilation and execution of a `hello.exs` file in IEX are demonstrated, including handling a warning about redefining a module. It shows how to call functions, such as `hello.world`, with optional parentheses and how to pass parameters using string interpolation. It also explains that the `OK` result in IEX is an atom, and that all data types in Elixir are immutable.
- Elixir code is compiled into a
.beamfile, which is executed on the Beam virtual machine. - The
mixtool is typically used for project management in Elixir. - Elixir files with the
.exsextension can be compiled and executed directly in the interactive shell IEX. - When recompiling a module in IEX, a warning about redefining the module may appear, which can be ignored.
- Calling functions in Elixir can be done using the
Module.functionnotation, with parentheses around arguments being optional, especially if the function takes no parameters. - The
OKresult in IEX is an atom, representing a data type. - All data types in Elixir are immutable.
- Elixir supports string interpolation using the
#{variable}syntax. - The
Rcommand in IEX is used to recompile a module.
Atoms in Elixir
## Atoms in Elixir Atoms in Elixir are named constants. They start with a colon (`:`), followed by the atom's name. If the atom's name contains spaces, it is enclosed in double quotes. The syntax for an atom is: `:atom_name` or `:'atom name with spaces'`. Atoms are used to represent fixed values, often appearing in pattern matching, for example, to indicate errors (`:error`) or states.
- An atom in Elixir starts with a colon (
:), followed by a name. - If the atom's name contains spaces, it is enclosed in double quotes, e.g.,
:'atom name with spaces'. - Atoms are used to represent fixed values.
- Atoms are widely used in pattern matching.
- An example of using an atom to indicate an error:
:error.
Tuples in Elixir
## Tuples in Elixir Tuples in Elixir are often used to represent the results of operations, especially for indicating errors or successful operations. A typical pattern involves two- or three-element tuples. ### Using Tuples for Errors A tuple can contain the atom `:error` as its first element, followed by a description of the error reason, such as the string `"file not found"`. This pattern allows for pattern matching to extract information. Example: ```elixir {:error, "file not found"} ``` Pattern Matching: ```elixir {:error, reason} = {:error, "file not found"} # The variable `reason` now holds the value "file not found" ``` ### Using Tuples for Successful Operations Similarly, tuples are used to represent successful results. For instance, in a web application, a successful response might be represented by a tuple with the atom `:ok` and a message or data. Example: ```elixir {:ok, "status 200 okay"} ``` Pattern Matching for a Successful Result: ```elixir {:ok, message} = {:ok, "status 200 okay"} # The variable `message` now holds the value "status 200 okay" ``` Pattern matching is a key mechanism for working with tuples in Elixir, allowing easy extraction of values from their structure.
- Tuples in Elixir are used to represent the results of operations.
- Typical tuples have two or three elements.
- Tuples are often used to indicate errors, where the first element is the atom
:errorand the second is the error reason (e.g., the string"file not found"). - Pattern matching is widely used for working with tuples.
- In pattern matching, the structure on the left must match the structure on the right.
- Variables in the pattern on the left are bound to values from the tuple on the right.
- Tuples are also used to represent successful operations, for example, with the atom
:okand a message (e.g.,"status 200 okay"). - An example of a successful tuple is:
{:ok, "status 200 okay"}. - Pattern matching allows extracting values from successful tuples by binding them to variables (e.g.,
message).
Strings and Lists in Elixir
In Elixir, strings are represented by double quotes. Single quotes are used for character lists, which are different from strings. Strings in Elixir are stored as a collection of bytes and are UTF-8 encoded binary data. The string "octalium" is 9 bytes in size. In IEX, the `is` function can be used to get information about a variable's data type.
- Strings in Elixir are represented by double quotes.
- Single quotes in Elixir denote a character list, not a string.
- Strings in Elixir are stored as a collection of bytes.
- A string in Elixir is a UTF-8 encoded binary value.
- In IEX, the
isfunction is used to display information about a variable's data type. - Example: the string "octalium" has a data type of "binary string" and a size of 9 bytes.
Working with Bytes and Protocols
This section covers the fundamentals of working with strings and protocols in Elixir, including their representation as bytes, using pattern matching for data extraction, and string concatenation. It also touches upon char lists, processes, lists (linked lists), tuples, keyword lists, and maps. Special attention is given to pattern matching for various data structures and using built-in functions to manipulate them. The conclusion discusses structs and their definition using `defmodule` and `defstruct` macros, as well as the basics of control flow with `case`, `cond`, and `if/else`. It covers creating a new project using `mix new` and the fundamentals of recursion, including tail recursion and recursive trees. Examples of implementing recursive functions for calculating factorial, sum of digits, and reversing a list are provided. The use of built-in functions for list manipulation, such as `Enum.map`, `Enum.reduce`, `Enum.filter`, and `Enum.sort`, and creating custom implementations of these functions are also demonstrated. The end of the section discusses creating statistics, including calculating the mean (population mean and sample mean), median, and mode, as well as the basics of working with structs for data representation, such as expenses and the seven wonders of the world, using built-in modules and functions for their processing.
- Strings in Elixir are represented as a collection of bytes or code points.
- The integer representation of a string's character can be obtained using
?character. - Protocols are a more advanced topic and are not covered in detail.
- Pattern matching is used to extract data from strings.
- String concatenation is done using single angle brackets
<>. Double angle brackets<<>>are used for raw byte representation. - The
is_binary/1function checks if a variable is a string (binary). - Char lists are created using single quotes and are represented as a list of integers (code points).
- Char lists are concatenated using the
++operator. - The
is_list/1function checks if a variable is a char list. - Processes in Elixir have a process identifier (PID), which can be obtained using
self(). - Lists in Elixir are singly linked lists.
- Direct access to list elements by index is not supported; recursive functions or the
Enummodule are used. - The
Enummodule provides many functions for working with enumerable data types. - The
h(help) helper function is used to get documentation for modules and functions. - Pattern matching can be used to extract elements from lists, ignoring unnecessary elements with
_. - The
head/1andtail/1functions return the first element of a list and the rest of the list, respectively. - The
|(pipe) operator is used as theconsoperator to prepend an element to a linked list. - Tuples are created using curly braces
{}and have a fixed size. - Tuples store data contiguously in memory and are often used to return multiple values from a function.
- Keyword lists are lists of key-value pairs, where keys are typically atoms.
- Accessing values in keyword lists is done using built-in functions from the
Keywordmodule. - Maps are created using the
%{}sigil, where keys can be of various types (atoms, strings, etc.). - Atom keys in maps allow for dot notation (
map.key) to access values. - String keys in maps require arrow notation (
map["key"]) to access values. - Structs are defined within modules using the
defstructmacro and inherit the module name. - Structs behave like maps and support pattern matching.
- Control flow is managed using
case,cond, andif/else. caseis used for pattern matching an expression against different clauses.condis used for checking multiple conditions sequentially.if/elseis used for simple conditional expressions.- Mix is Elixir's build tool, used for creating new projects (
mix new). - Recursion is a fundamental concept in functional programming where a function calls itself.
- A base case is necessary to terminate recursion.
- Tail recursion is recursion where the recursive call is the last operation in the function, allowing for memory optimization.
- Functions in Elixir are immutable.
- The
IOmodule is used for input/output, e.g.,IO.puts/1for printing text. - A Mix project is compiled and run using
iex -S mix. - Aliases (
alias) are used to shorten long module names. - Recompiling changed modules is done using
recompile(). - Recursive trees help visualize the execution of recursive functions.
- Functions can be overloaded by defining multiple versions with different parameters (pattern matching).
- The sum of list elements can be computed recursively.
- The factorial of a number can be computed recursively.
- Reversing a list is done by iteratively prepending elements to an accumulator.
- The
Enum.map/2function applies a function to each element of a list and returns a new list. - The
Enum.reduce/3function folds a list into a single value by applying a function to each element and an accumulator. - The
Enum.filter/2function returns a new list containing only elements for which the predicate function returnedtrue. - The
Enum.sort/2function sorts a list. - The
Enum.flat_map/2function applies a function to each element and then flattens the result.