Built-In Elixir Modules
Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.9", override: true},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
Navigation
Home Report An Issue MeasurementsFilter Values By TypeReview Questions
Upon completing this lesson, a student should be able to answer the following questions.
- Given you’re working with a particular data type such as an integer or map, where in the Elixir documentation should you look for relevant functionality?
- How do you safely inspect any data type?
- What are some common built-in functions developers use the most, and how do you use them?
Overview
Reinventing The Wheel
Rather than re-invent the wheel, we can rely upon already built tools to solve a range of common problems. Elixir provides built-in modules we can leverage to accomplish a wide variety of behavior without building our own custom solution.
Information Overload
The standard Elixir library contains a massive number of modules and functions. It’s not reasonable to expect your self to memorize all of these functions.
Instead, you should aspire to learn the most common modules and functions that you use everyday, and develop enough familiarity with the standard library to know where to look on HexDocs to find the appropriate tool when you need it.
Kernel
The Elixir Kernel module is part of the standard library in the Elixir programming language. It provides a number of basic functions for working with the underlying operating system, as well as functions that are core to Elixir.
Modules For Data Types
Most common data types have an associated module which contains functionality related to that data type.
For example:
Modules For Behavior
Elixir groups many modules into related behavior. For example, we’ve already seen the Enum module for data-agnostic enumeration.
The Kernel Module
The Kernel is the core of everything Elixir.
Even the operators you’ve already used are simply an alternative syntax to using functions in the Kernel.
Kernel.+(3, 3)
Kernel.elem/2
The Kernel.elem/2 function can retrieve an element from a tuple given an index.
Kernel.elem({3, 6, 9}, 0)
We can use Kernel functions with or without using the Kernel namespace.
elem({3, 6, 9}, 0)
Your Turn
Use Kernel.elem/2 to retrieve 100
from the following tuple.
Example solution
tuple = {0, 4, 1, 100, 5, 7}
elem(tuple, 3)
tuple = {0, 4, 1, 100, 5, 7}
Checking Types
Kernel contains many functions for determining a values type such as is_atom/1
, is_binary/1
, is_map/1
, and is_integer/1
.
true = Kernel.is_map(%{})
true = Kernel.is_atom(:an_atom)
true = Kernel.is_binary("")
true = Kernel.is_integer(1)
Your Turn
Use the Kernel module to check the types of each value in the cells below. You may have to read through the Kernel Documentation to find the appropriate functions.
Example solution
is_atom(:example)
is_map(%{})
is_binary("")
is_integer(1)
is_float(1.0)
is_boolean(true)
is_list([])
is_tuple({})
The first cell is filled out for sake of example. The result of each cell should be true
.
is_atom(:example)
%{}
{}
[]
true
1.0
1
""
The Kernel is reasonably large. Remember, our goal is not to memorize every function but to develop familiarity with repeated practice.
Max And Min
We can use the max/2 and min/2 to return the largest (max) or smallest (min) value between two numbers.
max(100, 110)
min(100, 110)
Your Turn
We often use max/2 and min/2 to prevent a value from going above or below a value.
For example, let’s say we have a seconds
variable that represents seconds on a clock.
We want to prevent seconds
from being less than 0
seconds, and more than 59
seconds.
Created a capped_seconds
variable that uses the value of seconds
and cannot go above 59
seconds or below 0
seconds.
Example solution
seconds = 61
capped_seconds = max(min(seconds, 59), 0)
Enter your solution below. if seconds
is less than 0
capped_seconds
should be 0
. If seconds
is greater than 59
, then capped_seconds
should be 59
.
seconds = 60
capped_seconds = nil
Safe Inspection
Not all values can be interpolated inside of a string. Attempting to do so results in a protocol String.Chars not implemented
error.
map = %{}
"#{map}"
String.Chars
is a protocol for converting data types into a string. We’ll learn more about protocols in a future lesson. It’s enough for our purposes to know that this means the data type does not know how to be safely converted to a string.
We can use Kernel.inspect/2 to safely convert any Elixir term into a string.
inspect(%{})
Which allows us to use values that don’t implement the String.Chars
protocol
map = %{}
"#{inspect(map)}"
Your Turn
Use Kernel.inspect/2 to safely interpolate the following variables inside of a string.
Example solution
map = %{}
list = [1, 2, 3]
tuple = {1, 2, 3}
"#{inspect(map)} #{inspect(list)} #{inspect(tuple)}"
map = %{}
list = [1, 2, 3]
tuple = {1, 2, 3}
""
The Integer Module
The Integer module contains functionality related to Integers.
We’ve already seen the Integer module in the Non-Enumerables reading material using Integer.digits/2.
Integer.digits(123_456_789)
As well as Integer.undigits/2.
Integer.undigits([1, 2, 3, 4, 5, 6, 7, 8, 9])
Parsing Integers From Strings
We can use Integer.parse/2 to parse an integer from a string. It returns a {integer, rest_of_string}
tuple.
Integer.parse("2")
That makes it especially useful when we have a string with non-integer input such as a newline character \n
.
user_input = "25\n"
Integer.parse(user_input)
Your Turn
Open the IEx shell by running iex
in your command line. You must have Elixir installed locally for this to work.
$ iex
This should open the IEx Shell.
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
Interactive Elixir (1.13.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
You can use IO.gets/2 to retrieve user input in this environment (this does not work in Livebook because we do not have a Livebook console)
Use Integer.parse/1 to convert the string input into an integer.
iex(1)> IO.gets("Give me a number! ")
Give me a number! 25
"25\n"
iex(2)> IO.gets("Give me a number! ") |> Integer.parse()
Give me a number! 25
{25, "\n"}
Integer Math Functions
The Integer module includes several math-related functions such as Integer.gcd/2 (greatest common denominator), Integer.pow/2, Integer.mod/2, and others.
Likely, you’ll use the arithmetic operators and functions from the Kernel module and will rarely need to rely on these functions in the Integer module so we won’t go over them in greater detaill.
Your Turn
Use the Integer.gcd/2 function to determine the greatest common denominator of 10
and 15
. The greatest common divisor (GCD) is the largest
positive integer that divides both 10 and 15 evenly, so the result should be 5
.
The String Module
The String module contains functionality related to strings.
Here are a few common functions to get you started.
- String.at/2 gets the value at the index of a string.
- String.contains?/2 check if a string contains a value.
- String.capitalize/2 capitalize the first letter in a string and leaves the rest lowercase.
- String.downcase/2 convert a string to all lowercase.
- String.split/3 split the string into a list of words.
- String.trim/1 remove whitespace from a string.
- String.upcase/2 convert a string to ALL CAPS.
Indexes
You can imagine a string like a list of characters. However, be warned this is only a mental model, strings are not actually implemented as lists they are implemented as binaries.
flowchart TB
subgraph Characters
S
T
R
I
N
G
end
subgraph Indexes
direction TB
S --- 0
T --- 1
R --- 2
I --- 3
N --- 4
G --- 5
end
Notice that the index starts with 0, not 1, just like lists.
So the character at index 1 in "hello"
would be "e"
.
flowchart TB
subgraph Characters
H
E
L1[L]
L2[L]
O
end
subgraph Indexes
direction TB
H --- 0
E --- 1
L1 --- 2
L2 --- 3
O --- 4
end
Your Turn: String Exercises
Use the String.at/2 function to get the character at index 2
of "hello"
. The result should be the character l
.
Example solution
String.at("hello", 2)
"hello"
Use the String.at/2 function to retrieve the letter "o"
in "hello"
Example solution
String.at("hello", 4)
"hello"
Use the String.contains?/2 function to determine if "hello"
contains "lo"
. The result should be true
.
Example solution
String.contains("hello", "lo")
"Hello"
Use String.capitalize/2 to capitalize "hello"
to "Hello"
.
Example solution
String.capitalize("hello")
"hello"
Use String.upcase/2 to upcase "hello"
to "HELLO"
.
Example solution
String.upcase("hello")
"hello"
Use String.downcase/2 to dowcase "HELLO"
to "hello"
.
Example solution
String.downcase("HELLO")
"HELLO"
Use String.split/3 to split the following comma-separated list of strings into a list of words.
Example solution
String.split("have,a,great,day", ",")
"have,a,great,day"
Use String.trim/1 to remove whitespace from the " hello! "
string.
Example solution
String.trim(" hello! ")
" hello! "
The List Module
The List module contains functionality related to lists.
Here are a few common functions to get you started.
- List.delete_at/2 remove an element at an index in a list.
-
List.first/2 retrieve the first element in a list similar to
hd/1
or pattern matching on the head and tail of a list. - List.flatten/2 flatten nested lists within a list.
- List.insert_at/3 insert an element at a specified index within a list.
- List.last/2 retrieve the last element in a list.
- List.update_at/3 update an element at a specified index within a list.
- List.zip/1 combine elements from multiple lists into a single list of tuples.
Mutation
Remember that in Elixir we do not mutate variables. That means the List.delete_at/2, List.insert_at/3 and List.update_at/3 functions do not mutate the original list. Instead, they create a new copy of the original list with the operation applied to it.
list = [1, 2, 3]
List.delete_at(list, 1)
Notice the original list
variable has not changed.
list
Retrieving The First Element In A List
We have many different ways of retrieving the first element in a list. List.first/2 retrieves the first element in a list. We can also use Kernel.hd/1, Enum.at/2, or pattern matching.
head = List.first([1, 2, 3])
[head | _tail] = [1, 2, 3]
head
hd([1, 2, 3])
head = Enum.at([1, 2, 3], 0)
Try pattern matching, List.first/2, Enum.at/2, and hd/1
to retrieve the first element of
the list below.
Your Turn: List Exercises
Use List.delete_at/2 to remove 2
from this list.
Example solution
List.delete_at([2, 1, 3], 0)
[2, 1, 3]
Use List.flatten/1 to flatten the following list into [1, 2, 3, 4, 5, 6, 7, 8, 9]
Example solution
List.flatten([1, 2, [3, 4, 5], 6, [7, [8, [9]]]])
[1, 2, [3, 4, 5], 6, [7, [8, [9]]]]
Use List.insert_at/3 to insert 2
into the following list to make [1, 2, 3]
.
Example solution
List.insert_at([1, 3], 1, 2)
[1, 3]
Use List.last/2 to retrieve the last element 10000
in a list from 1
to 10000
.
Example solution
List.last(Enum.to_list(1..10000))
You might also use the piper operator |>
.
1..10000 |> Enum.to_list() |> List.last()
Enum.to_list(1..10000)
Use List.update_at/3 to subtract 2
from 4
in the following list to make [1, 2, 3]
.
Example solution
List.update_at([1, 4, 3], 1, fn elem -> elem - 2 end)
[1, 4, 3]
Use List.zip/1 to combine these two lists to make [{"a", 1}, {"b", 2}, {"c", 3}]
.
Example solution
letters = ["a", "b", "c"]
numbers = [1, 2, 3]
List.zip([letters, numbers])
letters = ["a", "b", "c"]
numbers = [1, 2, 3]
The Map Module
The Map module contains functionality related to maps.
Here are a few common functions to get you started.
- Map.get/3 retrieve values in a map.
- Map.put/3 put a value into a map.
- Map.keys/1 list the keys in a map.
- Map.delete/2 remove a key and value from a map.
- Map.merge/2 merge two maps together.
- Map.update/4 and Map.update!/3 update a map using the existing value of the updated key.
- Map.values/1 list the values in a map.
Mutation
Once again, Map module functions do not mutate a value.
For example, if we use Map.put/3 to put a new value in a map, the original variable is not changed. Functions return a new value rather than modifying the original one.
original_map = %{}
new_map = Map.put(original_map, :key, "value")
So the original_map
is still an empty map %{}
.
original_map
And the new_map
has been bound to the result of Map.put/3.
new_map
Your Turn: Map Exercises
Use Map.get/3 to retrieve the "world"
value for the :hello
key in the following map.
Example solution
Map.get(%{hello: "world"}, :hello)
%{hello: "world"}
Use Map.put/3 to add the key :two
with the value 2
to the following map.
Example solution
Map.put(%{one: 1}, :two, 2)
%{one: 1}
Use Map.keys/1 to retrieve the keys for the following map.
Example solution
Map.keys(%{key1: 1, key2: 2, key3: 3})
%{key1: 1, key2: 2, key3: 3}
Use Map.delete/2 to remove :key1
from the following map.
Example solution
Map.delete(%{key1: 1, key2: 2, key3: 3}, :key1)
%{key1: 1, key2: 2, key3: 3}
Use Map.merge/2 to combine %{one: 1}
and %{two: 2}
.
Example Solution
Map.merge(%{one: 1}, %{two: 2})
Use Map.update/4 or Map.update!/3 to update the :count
key in this map to be 5
plus the existing value.
Example Solution
Map.update(%{count: 10}, :count, 0, fn count -> count + 5 end)
%{count: 10}
Use Map.values/1 to retrieve the values [1, 2, 3]
in the following map.
Example solution
Map.values(%{key1: 1, key2: 2, key3: 3})
%{key1: 1, key2: 2, key3: 3}
The Keyword Module
The Keyword module contains functionality related to keyword lists.
Here are a few common functions to get you started.
- Keyword.get/3 retrieve values in a keyword list.
- Keyword.keys/1 list the keys in a keyword list.
- Keyword.keyword?/1 check if some data is a keyword list.
Options
We often use keyword lists to provide optional arguments to a function.
For example, IO.inspect/2 has many optional arguments
Including :label
.
IO.inspect("world", label: "hello")
Under the hood, these functions may use Keyword.get/3 to retrieve optional arguments or provide default arguments.
It’s common to pass any number of options in a opts
parameter, which should be the last parameter of the function.
defmodule MyIO do
def inspect(value, opts \\ []) do
label = Keyword.get(opts, :label, "default label")
"#{label}: #{value}"
end
end
MyIO.inspect("world")
MyIO.inspect("world", label: "hello")
Your Turn: Keyword Exercises
Use Keyword.get/3 to access the value for the :color
key in the following keyword list.
Example solution
Keyword.get([color: "red"], :color)
[color: "red"]
Use Keyword.get/3 to access the value for the :color
key in the following empty list. If the :color
key does not exist, provide a default value of "blue"
.
Example solution
Keyword.get([], :color, "blue")
[]
Use the Keyword.keys/1 function to list all of the keys in the following keyword list.
Example solution
Keyword.keys([one: 1, two: 2, three: 3])
[one: 1, two: 2, three: 3]
Use the Keyword.keyword?/1 function to determine if the following is a keyword list.
Example solution
Keyword.keyword?([key: "value"])
[key: "value"]
Use the Keyword.keyword?/1 function to determine if an empty list is a keyword list.
Example solution
An empty list is technically a keyword list. That’s because there’s no difference between an empty keyword list and an empty list.
Technically, all of the elements in an empty list follow the {:atom, value}
pattern that a keyword list enforces. Or perhaps it’s better to say that no elements violate the pattern.
Keyword.keyword?([])
[]
Use the Keyword.keyword?/1 function to determine if the following list is a keyword list.
Example solution
A list is not a keyword list if it has any elements that don’t follow the {:atom, term}
structure.
Keyword.keyword?([1, 2, 3])
[1, 2, 3]
Further Reading
Consider the following resource(s) to deepen your understanding of the topic.
- Hexdocs: Kernel
- Hexdocs: Integer
- Hexdocs: String
- Hexdocs: List
- Hexdocs: Map
- Hexdocs: Keyword
- Hexdocs: Tuple
Commit Your Progress
DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.
Run git status
to ensure there are no undesirable changes.
Then run the following in your command line from the curriculum
folder to commit your progress.
$ git add .
$ git commit -m "finish Built-In Elixir Modules reading"
$ git push
We’re proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.
We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.