Category: Elixir

  • Libraries

    Elixir provides excellent interoperability with Erlang libraries. Let us discuss a few libraries in brief.

    The Binary Module

    The built-in Elixir String module handles binaries that are UTF-8 encoded. The binary module is useful when you are dealing with binary data that is not necessarily UTF-8 encoded. Let us consider an example to further understand the Binary module −

    # UTF-8
    IO.puts(String.to_char_list("Ø"))
    
    # binary
    IO.puts(:binary.bin_to_list "Ø")

    When the above program is run, it produces the following result −

    [216]
    [195, 152]
    

    The above example shows the difference; the String module returns UTF-8 codepoints, while :binary deals with raw data bytes.

    The Crypto Module

    The crypto module contains hashing functions, digital signatures, encryption and more. This module is not part of the Erlang standard library, but is included with the Erlang distribution. This means you must list :crypto in your project’s applications list whenever you use it. Let us see an example using the crypto module −

    IO.puts(Base.encode16(:crypto.hash(:sha256, "Elixir")))

    When the above program is run, it produces the following result −

    3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB
    

    The Digraph Module

    The digraph module contains functions for dealing with directed graphs built of vertices and edges. After constructing the graph, the algorithms in there will help finding, for instance, the shortest path between two vertices, or loops in the graph. Note that the functions in :digraph alter the graph structure indirectly as a side effect, while returning the added vertices or edges.

    digraph = :digraph.new()
    coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
    [v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
    :digraph.add_edge(digraph, v0, v1)
    :digraph.add_edge(digraph, v1, v2)
    for point <- :digraph.get_short_path(digraph, v0, v2) do 
       {x, y} = point
       IO.puts("#{x}, #{y}")
    end

    When the above program is run, it produces the following result −

    0.0, 0.0
    1.0, 0.0
    1.0, 1.0
    

    The Math Module

    The math module contains common mathematical operations covering trigonometry, exponential and logarithmic functions. Let us consider the following example to understand how the Math module works −

    # Value of pi
    IO.puts(:math.pi())
    
    # Logarithm
    IO.puts(:math.log(7.694785265142018e23))
    
    # Exponentiation
    IO.puts(:math.exp(55.0))
    
    #...

    When the above program is run, it produces the following result −

    3.141592653589793
    55.0
    7.694785265142018e23
    

    The Queue Module

    The queue is a data structure that implements (double-ended) FIFO (first-in first-out) queues efficiently. The following example shows how a Queue module works −

    q = :queue.new
    q = :queue.in("A", q)
    q = :queue.in("B", q)
    {{:value, val}, q} = :queue.out(q)
    IO.puts(val)
    {{:value, val}, q} = :queue.out(q)
    IO.puts(val)

    When the above program is run, it produces the following result −

    A
    B
    
  • Errors Handling

    Elixir has three error mechanisms: errors, throws and exits. Let us explore each mechanism in detail.

    Error

    Errors (or exceptions) are used when exceptional things happen in the code. A sample error can be retrieved by trying to add a number into a string −

    IO.puts(1 + "Hello")

    When the above program is run, it produces the following error −

    ** (ArithmeticError) bad argument in arithmetic expression
       :erlang.+(1, "Hello")
    

    This was a sample inbuilt error.

    Raising Errors

    We can raise errors using the raise functions. Let us consider an example to understand the same −

    #Runtime Error with just a message
    raise "oops"  # ** (RuntimeError) oops

    Other errors can be raised with raise/2 passing the error name and a list of keyword arguments

    #Other error type with a message
    raise ArgumentError, message: "invalid argument foo"

    You can also define your own errors and raise those. Consider the following example −

    defmodule MyError do
       defexception message: "default message"
    end
    
    raise MyError  # Raises error with default message
    raise MyError, message: "custom message"  # Raises error with custom message

    Rescuing Errors

    We do not want our programs to abruptly quit but rather the errors need to be handled carefully. For this we use error handling. We rescue errors using the try/rescue construct. Let us consider the following example to understand the same −

    err = try do
       raise "oops"
    rescue
       e in RuntimeError -> e
    end
    
    IO.puts(err.message)

    When the above program is run, it produces the following result −

    oops
    

    We have handled errors in the rescue statement using pattern matching. If we do not have any use of the error, and just want to use it for identification purposes, we can also use the form −

    err = try do
       1 + "Hello"
    rescue
       RuntimeError -> "You've got a runtime error!"
       ArithmeticError -> "You've got a Argument error!"
    end
    
    IO.puts(err)

    When running above program, it produces the following result −

    You've got a Argument error!
    

    NOTE − Most functions in the Elixir standard library are implemented twice, once returning tuples and the other time raising errors. For example, the File.read and the File.read! functions. The first one returned a tuple if the file was read successfully and if an error was encountered, this tuple was used to give the reason for the error. The second one raised an error if an error was encountered.

    If we use the first function approach, then we need to use case for pattern matching the error and take action according to that. In the second case, we use the try rescue approach for error prone code and handle errors accordingly.

    Throws

    In Elixir, a value can be thrown and later be caught. Throw and Catch are reserved for situations where it is not possible to retrieve a value unless by using throw and catch.

    The instances are quite uncommon in practice except when interfacing with libraries. For example, let us now assume that the Enum module did not provide any API for finding a value and that we needed to find the first multiple of 13 in a list of numbers −

    val = try do
       Enum.each 20..100, fn(x) ->
    
      if rem(x, 13) == 0, do: throw(x)
    end "Got nothing" catch x -> "Got #{x}" end IO.puts(val)

    When the above program is run, it produces the following result −

    Got 26
    

    Exit

    When a process dies of “natural causes” (for example, unhandled exceptions), it sends an exit signal. A process can also die by explicitly sending an exit signal. Let us consider the following example −

    spawn_link fn -> exit(1) end

    In the example above, the linked process died by sending an exit signal with value of 1. Note that exit can also be “caught” using try/catch. For example −

    val = try do
       exit "I am exiting"
    catch
       :exit, _ -> "not really"
    end
    
    IO.puts(val)

    When the above program is run, it produces the following result −

    not really
    

    After

    Sometimes it is necessary to ensure that a resource is cleaned up after some action that can potentially raise an error. The try/after construct allows you to do that. For example, we can open a file and use an after clause to close it–even if something goes wrong.

    {:ok, file} = File.open "sample", [:utf8, :write]
    try do
       IO.write file, "olá"
       raise "oops, something went wrong"
    after
       File.close(file)
    end

    When we run this program, it will give us an error. But the after statement will ensure that the file descriptor is closed upon any such event.

  • Comprehensions

    List comprehensions are syntactic sugar for looping through enumerables in Elixir. In this chapter we will use comprehensions for iteration and generation.

    Basics

    When we looked at the Enum module in the enumerables chapter, we came across the map function.

    Enum.map(1..3, &(&1 * 2))

    In this example, we will pass a function as the second argument. Each item in the range will be passed into the function, and then a new list will be returned containing the new values.

    Mapping, filtering, and transforming are very common actions in Elixir and so there is a slightly different way of achieving the same result as the previous example −

    for n <- 1..3, do: n * 2

    When we run the above code, it produces the following result −

    [2, 4, 6]
    

    The second example is a comprehension, and as you can probably see, it is simply syntactic sugar for what you can also achieve if you use the Enum.map function. However, there are no real benefits to using a comprehension over a function from the Enum module in terms of performance.

    Comprehensions are not limited to lists but can be used with all enumerables.

    Filter

    You can think of filters as a sort of guard for comprehensions. When a filtered value returns false or nil it is excluded from the final list. Let us loop over a range and only worry about even numbers. We will use the is_even function from the Integer module to check if a value is even or not.

    import Integer
    IO.puts(for x <- 1..10, is_even(x), do: x)

    When the above code is run, it produces the following result −

    [2, 4, 6, 8, 10]
    

    We can also use multiple filters in the same comprehension. Add another filter that you want after the is_even filter separated by a comma.

    :into Option

    In the examples above, all the comprehensions returned lists as their result. However, the result of a comprehension can be inserted into different data structures by passing the :into option to the comprehension.

    For example, a bitstring generator can be used with the :into option in order to easily remove all spaces in a string −

    IO.puts(for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>)

    When the above code is run, it produces the following result −

    helloworld
    

    The above code removes all spaces from the string using c != ?\s filter and then using the :into option, it puts all the returned characters in a string.

  • Sigils

    In this chapter, we are going to explore sigils, the mechanisms provided by the language for working with textual representations. Sigils start with the tilde (~) character which is followed by a letter (which identifies the sigil) and then a delimiter; optionally, modifiers can be added after the final delimiter.

    Regex

    Regexes in Elixir are sigils. We have seen their use in the String chapter. Let us again take an example to see how we can use regex in Elixir.

    # A regular expression that matches strings which contain "foo" or
    # "bar":
    regex = ~r/foo|bar/
    IO.puts("foo" =~ regex)
    IO.puts("baz" =~ regex)

    When the above program is run, it produces the following result −

    true
    false
    

    Sigils support 8 different delimiters −

    ~r/hello/
    ~r|hello|
    ~r"hello"
    ~r'hello'
    ~r(hello)
    ~r[hello]
    ~r{hello}
    ~r<hello>
    

    The reason behind supporting different delimiters is that different delimiters can be more suited for different sigils. For example, using parentheses for regular expressions may be a confusing choice as they can get mixed with the parentheses inside the regex. However, parentheses can be handy for other sigils, as we will see in the next section.

    Elixir supports Perl compatible regexes and also support modifiers. You can read up more about the use of regexes here.

    Strings, Char lists and Word lists

    Other than regexes, Elixir has 3 more inbuilt sigils. Let us have a look at the sigils.

    Strings

    The ~s sigil is used to generate strings, like double quotes are. The ~s sigil is useful, for example, when a string contains both double and single quotes −

    new_string = ~s(this is a string with "double" quotes, not 'single' ones)
    IO.puts(new_string)

    This sigil generates strings. When the above program is run, it produces the following result −

    "this is a string with \"double\" quotes, not 'single' ones"
    

    Char Lists

    The ~c sigil is used to generate char lists −

    new_char_list = ~c(this is a char list containing 'single quotes')
    IO.puts(new_char_list)

    When the above program is run, it produces the following result −

    this is a char list containing 'single quotes'
    

    Word Lists

    The ~w sigil is used to generate lists of words (words are just regular strings). Inside the ~w sigil, words are separated by whitespace.

    new_word_list = ~w(foo bar bat)
    IO.puts(new_word_list)

    When the above program is run, it produces the following result −

    foobarbat
    

    The ~w sigil also accepts the c, s and a modifiers (for char lists, strings and atoms, respectively), which specify the data type of the elements of the resulting list −

    new_atom_list = ~w(foo bar bat)a
    IO.puts(new_atom_list)

    When the above program is run, it produces the following result −

    [:foo, :bar, :bat]
    

    Interpolation and Escaping in Sigils

    Besides lowercase sigils, Elixir supports uppercase sigils to deal with escaping characters and interpolation. While both ~s and ~S will return strings, the former allows escape codes and interpolation while the latter does not. Let us consider an example to understand this −

    ~s(String with escape codes \x26 #{"inter" <> "polation"})
    # "String with escape codes & interpolation"
    ~S(String without escape codes \x26 without #{interpolation})
    # "String without escape codes \\x26 without \#{interpolation}"
    

    Custom Sigils

    We can easily create our own custom sigils. In this example, we will create a sigil to convert a string to uppercase.

    defmodule CustomSigil do
       def sigil_u(string, []), do: String.upcase(string)
    end
    
    import CustomSigil
    
    IO.puts(~u/tutorials point/)

    When we run the above code, it produces the following result −

    TUTORIALS POINT
    

    First we define a module called CustomSigil and within that module, we created a function called sigil_u. As there is no existing ~u sigil in the existing sigil space, we will use it. The _u indicates that we wish use u as the character after the tilde. The function definition must take two arguments, an input and a list.

  • Processes

    In Elixir, all code runs inside processes. Processes are isolated from each other, run concurrent to one another and communicate via message passing. Elixir’s processes should not be confused with operating system processes. Processes in Elixir are extremely lightweight in terms of memory and CPU (unlike threads in many other programming languages). Because of this, it is not uncommon to have tens or even hundreds of thousands of processes running simultaneously.

    In this chapter, we will learn about the basic constructs for spawning new processes, as well as sending and receiving messages between different processes.

    The Spawn Function

    The easiest way to create a new process is to use the spawn function. The spawn accepts a function that will be run in the new process. For example −

    pid = spawn(fn -> 2 * 2 end)
    Process.alive?(pid)

    When the above program is run, it produces the following result −

    false
    

    The return value of the spawn function is a PID. This is a unique identifier for the process and so if you run the code above your PID, it will be different. As you can see in this example, the process is dead when we check to see if it alive. This is because the process will exit as soon as it has finished running the given function.

    As already mentioned, all Elixir codes run inside processes. If you run the self function you will see the PID for your current session −

    pid = self
     
    Process.alive?(pid)

    When the above program is run, it produces following result −

    true
    

    Message Passing

    We can send messages to a process with send and receive them with receive. Let us pass a message to the current process and receive it on the same.

    send(self(), {:hello, "Hi people"})
    
    receive do
       {:hello, msg} -> IO.puts(msg)
       {:another_case, msg} -> IO.puts("This one won't match!")
    end

    When the above program is run, it produces the following result −

    Hi people
    

    We sent a message to the current process using the send function and passed it to the PID of self. Then we handled the incoming message using the receive function.

    When a message is sent to a process, the message is stored in the process mailbox. The receive block goes through the current process mailbox searching for a message that matches any of the given patterns. The receive block supports guards and many clauses, such as case.

    If there is no message in the mailbox matching any of the patterns, the current process will wait until a matching message arrives. A timeout can also be specified. For example,

    receive do
       {:hello, msg}  -> msg
    after
       1_000 -> "nothing after 1s"
    end

    When the above program is run, it produces the following result −

    nothing after 1s
    

    NOTE − A timeout of 0 can be given when you already expect the message to be in the mailbox.

    Links

    The most common form of spawning in Elixir is actually via spawn_link function. Before taking a look at an example with spawn_link, let us understand what happens when a process fails.

    spawn fn -> raise "oops" end

    When the above program is run, it produces the following error −

    [error] Process #PID<0.58.00> raised an exception
    ** (RuntimeError) oops
       :erlang.apply/2
    

    It logged an error but the spawning process is still running. This is because processes are isolated. If we want the failure in one process to propagate to another one, we need to link them. This can be done with the spawn_link function. Let us consider an example to understand the same −

    spawn_link fn -> raise "oops" end

    When the above program is run, it produces the following error −

    ** (EXIT from #PID<0.41.0>) an exception was raised:
       ** (RuntimeError) oops
    
      :erlang.apply/2

    If you are running this in iex shell then the shell handles this error and does not exit. But if you run by first making a script file and then using elixir <file-name>.exs, the parent process will also be brought down due to this failure.

    Processes and links play an important role when building fault-tolerant systems. In Elixir applications, we often link our processes to supervisors which will detect when a process dies and start a new process in its place. This is only possible because processes are isolated and don’t share anything by default. And since processes are isolated, there is no way a failure in a process will crash or corrupt the state of another. While other languages will require us to catch/handle exceptions; in Elixir, we are actually fine with letting processes fail because we expect supervisors to properly restart our systems.

    State

    If you are building an application that requires state, for example, to keep your application configuration, or you need to parse a file and keep it in memory, where would you store it? Elixir’s process functionality can come in handy when doing such things.

    We can write processes that loop infinitely, maintain state, and send and receive messages. As an example, let us write a module that starts new processes that work as a key-value store in a file named kv.exs.

    defmodule KV do
       def start_link do
    
      Task.start_link(fn -&gt; loop(%{}) end)
    end defp loop(map) do
      receive do
         {:get, key, caller} -&gt;
         send caller, Map.get(map, key)
         loop(map)
         {:put, key, value} -&gt;
         loop(Map.put(map, key, value))
      end
    end end

    Note that the start_link function starts a new process that runs the loop function, starting with an empty map. The loop function then waits for messages and performs the appropriate action for each message. In the case of a :get message, it sends a message back to the caller and calls loop again, to wait for a new message. While the :put message actually invokes loop with a new version of the map, with the given key and value stored.

    Let us now run the following −

    iex kv.exs

    Now you should be in your iex shell. To test out our module, try the following −

    {:ok, pid} = KV.start_link
    
    # pid now has the pid of our new process that is being 
    # used to get and store key value pairs 
    
    # Send a KV pair :hello, "Hello" to the process
    send pid, {:put, :hello, "Hello"}
    
    # Ask for the key :hello
    send pid, {:get, :hello, self()}
    
    # Print all the received messages on the current process.
    flush()

    When the above program is run, it produces the following result −

    "Hello"
    
  • Maps

    Keyword lists are a convenient way to address content stored in lists by key, but underneath, Elixir is still walking through the list. That might be suitable if you have other plans for that list requiring walking through all of it, but it can be an unnecessary overhead if you are planning to use keys as your only approach to the data.

    This is where maps come to your rescue. Whenever you need a key-value store, maps are the “go to” data structure in Elixir.

    Creating a Map

    A map is created using the %{} syntax −

    map = %{:a => 1, 2 => :b}

    Compared to the keyword lists, we can already see two differences −

    • Maps allow any value as a key.
    • Maps’ keys do not follow any ordering.

    Accessing a key

    In order to acces value associated with a key, Maps use the same syntax as Keyword lists −

    map = %{:a => 1, 2 => :b}
    IO.puts(map[:a])
    IO.puts(map[2])

    When the above program is run, it generates the following result −

    1
    b
    

    Inserting a key

    To insert a key in a map, we use the Dict.put_new function which takes the map, new key and new value as arguments −

    map = %{:a => 1, 2 => :b}
    new_map = Dict.put_new(map, :new_val, "value") 
    IO.puts(new_map[:new_val])

    This will insert the key-value pair :new_val – “value” in a new map. When the above program is run, it generates the following result −

    "value"
    

    Updating a Value

    To update a value already present in the map, you can use the following syntax −

    map = %{:a => 1, 2 => :b}
    new_map = %{ map | a: 25}
    IO.puts(new_map[:a])

    When the above program is run, it generates the following result −

    25
    

    Pattern Matching

    In contrast to keyword lists, maps are very useful with pattern matching. When a map is used in a pattern, it will always match on a subset of the given value −

    Live Demo

    %{:a => a} = %{:a => 1, 2 => :b}
    IO.puts(a)

    The above program generates the following result −

    1
    

    This will match a with 1. And hence, it will generate the output as 1.

    As shown above, a map matches as long as the keys in the pattern exist in the given map. Therefore, an empty map matches all maps.

    Variables can be used when accessing, matching and adding map keys −

    n = 1
    map = %{n => :one}
    %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}

    The Map module provides a very similar API to the Keyword module with convenience functions to manipulate maps. You can use functions such as the Map.get, Map.delete, to manipulate maps.

    Maps with Atom keys

    Maps come with a few interesting properties. When all the keys in a map are atoms, you can use the keyword syntax for convenience −

    map = %{:a => 1, 2 => :b} 
    IO.puts(map.a) 

    Another interesting property of maps is that they provide their own syntax for updating and accessing atom keys −

    map = %{:a => 1, 2 => :b}
    IO.puts(map.a)

    The above program generates the following result −

    1
    
  • Keyword lists

    So far, we have not discussed any associative data structures, i.e., data structures that can associate a certain value (or multiple values) to a key. Different languages call these features with different names like dictionaries, hashes, associative arrays, etc.

    In Elixir, we have two main associative data structures: keyword lists and maps. In this chapter, we will focus on Keyword lists.

    In many functional programming languages, it is common to use a list of 2-item tuples as the representation of an associative data structure. In Elixir, when we have a list of tuples and the first item of the tuple (i.e. the key) is an atom, we call it a keyword list. Consider the following example to understand the same −

    list = [{:a, 1}, {:b, 2}]
    

    Elixir supports a special syntax for defining such lists. We can place the colon at the end of each atom and get rid of the tuples entirely. For example,

    list_1 = [{:a, 1}, {:b, 2}]
    list_2 = [a: 1, b: 2]
    IO.puts(list_1 == list_2)

    The above program will generate the following result −

    true
    

    Both of these represent a keyword list. Since keyword lists are also lists, we can use all the operations we used on lists on them.

    To retrieve the value associated with an atom in the keyword list, pass the atom as to [] after the name of the list −

    Live Demo

    list = [a: 1, b: 2]
    IO.puts(list[:a])

    The above program generates the following result −

    1
    

    Keyword lists have three special characteristics −

    • Keys must be atoms.
    • Keys are ordered, as specified by the developer.
    • Keys can be given more than once.

    In order to manipulate keyword lists, Elixir provides the Keyword module. Remember, though, keyword lists are simply lists, and as such they provide the same linear performance characteristics as lists. The longer the list, the longer it will take to find a key, to count the number of items, and so on. For this reason, keyword lists are used in Elixir mainly as options. If you need to store many items or guarantee one-key associates with a maximum one-value, you should use maps instead.

    Accessing a key

    To access values associated with a given key, we use the Keyword.get function. It returns the first value associated with the given key. To get all the values, we use the Keyword.get_values function. For example −

    kl = [a: 1, a: 2, b: 3] 
    IO.puts(Keyword.get(kl, :a)) 
    IO.puts(Keyword.get_values(kl)) 

    The above program will generate the following result −

    1
    [1, 2]
    

    Inserting a key

    To add a new value, use Keyword.put_new. If the key already exists, its value remains unchanged −

    kl = [a: 1, a: 2, b: 3]
    kl_new = Keyword.put_new(kl, :c, 5)
    IO.puts(Keyword.get(kl_new, :c))

    When the above program is run, it produces a new Keyword list with additional key, c and generates the following result −

    5
    

    Deleting a key

    If you want to delete all entries for a key, use Keyword.delete; to delete only the first entry for a key, use Keyword.delete_first.

    kl = [a: 1, a: 2, b: 3, c: 0]
    kl = Keyword.delete_first(kl, :b)
    kl = Keyword.delete(kl, :a)
    
    IO.puts(Keyword.get(kl, :a))
    IO.puts(Keyword.get(kl, :b))
    IO.puts(Keyword.get(kl, :c))

    This will delete the first b in the List and all the a in the list. When the above program is run, it will generate the following result −

    0
    
  • Lists and Tuples

    (Linked) Lists

    A linked list is a heterogeneous list of elements that are stored at different locations in memory and are kept track of by using references. Linked lists are data structures especially used in functional programming.

    Elixir uses square brackets to specify a list of values. Values can be of any type −

    [1, 2, true, 3]

    When Elixir sees a list of printable ASCII numbers, Elixir will print that as a char list (literally a list of characters). Whenever you see a value in IEx and you are not sure what it is, you can use the i function to retrieve information about it.

    Live Demo

    IO.puts([104, 101, 108, 108, 111])

    The above characters in the list are all printable. When the above program is run, it produces the following result −

    hello
    

    You can also define lists the other way round, using single quotes −

    Live Demo

    IO.puts(is_list('Hello'))

    When the above program is run, it produces the following result −

    true
    

    Keep in mind single-quoted and double-quoted representations are not equivalent in Elixir as they are represented by different types.

    Length of a List

    To find the length of a list, we use the length function as in the following program −

    Live Demo

    IO.puts(length([1, 2, :true, "str"]))

    The above program generates the following result −

    4
    

    Concatenation and Subtraction

    Two lists can be concatenated and subtracted using the ++ and  operators. Consider the following example to understand the functions.

    IO.puts([1, 2, 3] ++ [4, 5, 6])
    IO.puts([1, true, 2, false, 3, true] -- [true, false])

    This will give you a concatenated string in the first case and a subtracted string in the second. The above program generates the following result −

    [1, 2, 3, 4, 5, 6]
    [1, 2, 3, true]
    

    Head and Tail of a List

    The head is the first element of a list and the tail is the remainder of a list. They can be retrieved with the functions hd and tl. Let us assign a list to a variable and retrieve its head and tail.

    list = [1, 2, 3]
    IO.puts(hd(list))
    IO.puts(tl(list))

    This will give us the head and tail of the list as output. The above program generates the following result −

    1
    [2, 3]
    

    Note − Getting the head or the tail of an empty list is an error.

    Other List functions

    Elixir standard library provides a whole lot of functions to deal with lists. We will have a look at some of those here.

    S.no.Function Name and Description
    1delete(list, item)Deletes the given item from the list. Returns a list without the item. If the item occurs more than once in the list, just the first occurrence is removed.
    2delete_at(list, index)Produces a new list by removing the value at the specified index. Negative indices indicate an offset from the end of the list. If index is out of bounds, the original list is returned.
    3first(list)Returns the first element in list or nil if list is empty.
    4flatten(list)Flattens the given list of nested lists.
    5insert_at(list, index, value)Returns a list with value inserted at the specified index. Note that index is capped at the list length. Negative indices indicate an offset from the end of the list.
    6last(list)Returns the last element in list or nil if list is empty.

    Tuples

    Tuples are also data structures which store a number of other structures within them. Unlike lists, they store elements in a contiguous block of memory. This means accessing a tuple element per index or getting the tuple size is a fast operation. Indexes start from zero.

    Elixir uses curly brackets to define tuples. Like lists, tuples can hold any value −

    {:ok, "hello"}
    

    Length of a Tuple

    To get the length of a tuple, use the tuple_size function as in the following program −

    Live Demo

    IO.puts(tuple_size({:ok, "hello"}))
    

    The above program generates the following result −

    2
    

    Appending a Value

    To append a value to the tuple, use the Tuple.append function −

    tuple = {:ok, "Hello"}
    Tuple.append(tuple, :world)

    This will create and return a new tuple: {:ok, “Hello”, :world}

    Inserting a Value

    To insert a value at a given position, we can either use the Tuple.insert_at function or the put_elem function. Consider the following example to understand the same −

    tuple = {:bar, :baz}
    new_tuple_1 = Tuple.insert_at(tuple, 0, :foo)
    new_tuple_2 = put_elem(tuple, 1, :foobar)

    Notice that put_elem and insert_at returned new tuples. The original tuple stored in the tuple variable was not modified because Elixir data types are immutable. By being immutable, Elixir code is easier to reason about as you never need to worry if a particular code is mutating your data structure in place.

    Tuples vs. Lists

    What is the difference between lists and tuples?

    Lists are stored in memory as linked lists, meaning that each element in a list holds its value and points to the following element until the end of the list is reached. We call each pair of value and pointer a cons cell. This means accessing the length of a list is a linear operation: we need to traverse the whole list in order to figure out its size. Updating a list is fast as long as we are prepending elements.

    Tuples, on the other hand, are stored contiguously in memory. This means getting the tuple size or accessing an element by index is fast. However, updating or adding elements to tuples is expensive because it requires copying the whole tuple in memory.

  • Char lists

    A char list is nothing more than a list of characters. Consider the following program to understand the same.

    IO.puts('Hello')
    IO.puts(is_list('Hello'))

    The above program generates the following result −

    Hello
    true
    

    Instead of containing bytes, a char list contains the code points of the characters between single-quotes. So while the double-quotes represent a string (i.e. a binary), singlequotes represent a char list (i.e. a list). Note that IEx will generate only code points as output if any of the chars is outside the ASCII range.

    Char lists are used mostly when interfacing with Erlang, in particular old libraries that do not accept binaries as arguments. You can convert a char list to a string and back by using the to_string(char_list) and to_char_list(string) functions −

    IO.puts(is_list(to_char_list("hełło")))
    IO.puts(is_binary(to_string ('hełło')))

    The above program generates the following result −

    true
    true
    

    NOTE − The functions to_string and to_char_list are polymorphic, i.e., they can take multiple types of input like atoms, integers and convert them to strings and char lists respectively.

  • Strings

    Strings in Elixir are inserted between double quotes, and they are encoded in UTF-8. Unlike C and C++ where the default strings are ASCII encoded and only 256 different characters are possible, UTF-8 consists of 1,112,064 code points. This means that UTF-8 encoding consists of those many different possible characters. Since the strings use utf-8, we can also use symbols like: ö, ł, etc.

    Create a String

    To create a string variable, simply assign a string to a variable −

    str = "Hello world"

    To print this to your console, simply call the IO.puts function and pass it the variable str −

    str = str = "Hello world" 
    IO.puts(str)

    The above program generates the following result −

    Hello World
    

    Empty Strings

    You can create an empty string using the string literal, “”. For example,

    a = ""
    if String.length(a) === 0 do
       IO.puts("a is an empty string")
    end

    The above program generates the following result.

    a is an empty string
    

    String Interpolation

    String interpolation is a way to construct a new String value from a mix of constants, variables, literals, and expressions by including their values inside a string literal. Elixir supports string interpolation, to use a variable in a string, when writing it, wrap it with curly braces and prepend the curly braces with a ‘#’ sign.

    For example,

    x = "Apocalypse" 
    y = "X-men #{x}"
    IO.puts(y)

    This will take the value of x and substitute it in y. The above code will generate the following result −

    X-men Apocalypse
    

    String Concatenation

    We have already seen the use of String concatenation in previous chapters. The ‘<>’ operator is used to concatenate strings in Elixir. To concatenate 2 strings,

    x = "Dark"
    y = "Knight"
    z = x <> " " <> y
    IO.puts(z)

    The above code generates the following result −

    Dark Knight
    

    String Length

    To get the length of the string, we use the String.length function. Pass the string as a parameter and it will show you its size. For example,

    IO.puts(String.length("Hello"))

    When running above program, it produces following result −

    5
    

    Reversing a String

    To reverse a string, pass it to the String.reverse function. For example,

    IO.puts(String.reverse("Elixir"))

    The above program generates the following result −

    rixilE
    

    String Comparison

    To compare 2 strings, we can use the == or the === operators. For example,

    Live Demo

    var_1 = "Hello world"
    var_2 = "Hello Elixir"
    if var_1 === var_2 do
       IO.puts("#{var_1} and #{var_2} are the same")
    else
       IO.puts("#{var_1} and #{var_2} are not the same")
    end

    The above program generates the following result −

    Hello world and Hello elixir are not the same.
    

    String Matching

    We have already seen the use of the =~ string match operator. To check if a string matches a regex, we can also use the string match operator or the String.match? function. For example,

    IO.puts(String.match?("foo", ~r/foo/))
    IO.puts(String.match?("bar", ~r/foo/))

    The above program generates the following result −

    true 
    false
    

    This same can also be achieved by using the =~ operator. For example,

    IO.puts("foo" =~ ~r/foo/)

    The above program generates the following result −

    true
    

    String Functions

    Elixir supports a large number of functions related to strings, some of the most used are listed in the following table.

    Sr.No.Function and its Purpose
    1at(string, position)Returns the grapheme at the position of the given utf8 string. If position is greater than string length, then it returns nil
    2capitalize(string)Converts the first character in the given string to uppercase and the remainder to lowercase
    3contains?(string, contents)Checks if string contains any of the given contents
    4downcase(string)Converts all characters in the given string to lowercase
    5ends_with?(string, suffixes)Returns true if string ends with any of the suffixes given
    6first(string)Returns the first grapheme from a utf8 string, nil if the string is empty
    7last(string)Returns the last grapheme from a utf8 string, nil if the string is empty
    8replace(subject, pattern, replacement, options \\ [])Returns a new string created by replacing occurrences of pattern in subject with replacement
    9slice(string, start, len)Returns a substring starting at the offset start, and of length len
    10split(string)Divides a string into substrings at each Unicode whitespace occurrence with leading and trailing whitespace ignored. Groups of whitespace are treated as a single occurrence. Divisions do not occur on non-breaking whitespace
    11upcase(string)Converts all characters in the given string to uppercase

    Binaries

    A binary is just a sequence of bytes. Binaries are defined using << >>. For example:

    << 0, 1, 2, 3 >>
    

    Of course, those bytes can be organized in any way, even in a sequence that does not make them a valid string. For example,

    << 239, 191, 191 >>
    

    Strings are also binaries. And the string concatenation operator <> is actually a Binary concatenation operator:

    IO.puts(<< 0, 1 >> <> << 2, 3 >>)

    The above code generates the following result −

    << 0, 1, 2, 3 >>
    

    Note the ł character. Since this is utf-8 encoded, this character representation takes up 2 bytes.

    Since each number represented in a binary is meant to be a byte, when this value goes up from 255, it is truncated. To prevent this, we use size modifier to specify how many bits we want that number to take. For example −

    IO.puts(<< 256 >>) # truncated, it'll print << 0 >>
    IO.puts(<< 256 :: size(16) >>) #Takes 16 bits/2 bytes, will print << 1, 0 >>

    The above program will generate the following result −

    << 0 >>
    << 1, 0 >>
    

    We can also use the utf8 modifier, if a character is code point then, it will be produced in the output; else the bytes −

    IO.puts(<< 256 :: utf8 >>)

    The above program generates the following result −

    Ā
    

    We also have a function called is_binary that checks if a given variable is a binary. Note that only variables which are stored as multiples of 8bits are binaries.

    Bitstrings

    If we define a binary using the size modifier and pass it a value that is not a multiple of 8, we end up with a bitstring instead of a binary. For example,

    bs = << 1 :: size(1) >>
    IO.puts(bs)
    IO.puts(is_binary(bs))
    IO.puts(is_bitstring(bs))

    The above program generates the following result −

    << 1::size(1) >>
    false
    true
    

    This means that variable bs is not a binary but rather a bitstring. We can also say that a binary is a bitstring where the number of bits is divisible by 8. Pattern matching works on binaries as well as bitstrings in the same way.