Skip to content

Latest commit

 

History

History
983 lines (649 loc) · 25.5 KB

built-in_modules.livemd

File metadata and controls

983 lines (649 loc) · 25.5 KB

Built-In Elixir Modules

Mix.install([
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"},
  {:tested_cell, github: "brooklinjazz/tested_cell"},
  {:utils, path: "#{__DIR__}/../utils"}
])

Navigation

Return Home Report An Issue

Setup

Ensure you type the ea keyboard shortcut to evaluate all Elixir cells before starting. Alternatively, you can evaluate the Elixir cells as you read.

Overview

Elixir provides a great deal of functionality in built-in modules.

In general, each data type or common behavior has a built-in module containing related functions for dealing with that data type.

While it is unnecessary to memorize every function in every module, it's essential to be familiar with the built-in modules to know when to reach for them.

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.

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
Loading

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
Loading

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.

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.

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 technically counts as an empty list, because it does not have any elements that don't follow the {:atom, term} keyword list 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.

Commit Your Progress

Run the following in your command line from the beta_curriculum folder to track and save your progress in a Git commit.

$ git add .
$ git commit -m "finish built-in modules section"