Current section

Files

Jump to
livebook lib livebook notebook explore elixir_and_livebook.livemd
Raw

lib/livebook/notebook/explore/elixir_and_livebook.livemd

# Elixir and Livebook
## Introduction
In this notebook, we will explore some unique features when
using Elixir and Livebook together, such as inputs, autocompletion,
and more.
If you are not familiar with Elixir, there is a fast paced
introduction to the language in the [Distributed portals with
Elixir](/explore/notebooks/distributed-portals-with-elixir)
notebook. For a more structured introduction to the language,
see [Elixir's Getting Started guide](https://elixir-lang.org/getting-started/introduction.html)
and [the many learning resources available](https://elixir-lang.org/learning.html).
Let's move forward.
## Autocompletion
Elixir code cells also support autocompletion by
pressing <kbd>ctrl</kbd> + <kbd>␣</kbd>. The runtime must
have started for autocompletion to work. A simple way to
do so is by executing any code, such as the cell below:
```elixir
"Hello world"
```
Now try autocompleting the code below to `System.version()`.
First put the cursor after the `.` below and
press <kbd>ctrl</kbd>&nbsp;+&nbsp;<kbd>␣</kbd>:
```elixir
System.
```
You should have seen the editor listing many different options,
which you can use to find `version`. Executing the code will
return the Elixir version.
Note you can also press <kbd>tab</kbd> to cycle across the completion
alternatives.
## Using packages
Sometimes you need a dependency or two and notebooks are no exception to this.
In Livebook, you can use [`Mix.install/2`](https://hexdocs.pm/mix/Mix.html#install/2)
to bring dependencies into your notebook! This approach is especially useful when
sharing notebooks because everyone will be able to get the same dependencies.
Let's try this out:
**Note:** compiling dependencies may use a reasonable amount of memory. If you are
hosting Livebook, make sure you have enough memory allocated to the Livebook
instance, otherwise the command below will fail.
```elixir
Mix.install([
{:kino, "~> 0.5.0"}
])
```
[Kino](https://github.com/elixir-nx/kino) is a library that
allows you to control parts of Livebook directly from the Elixir
code. Let's use it to print a data table:
```elixir
data = [
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
]
Kino.DataTable.new(data)
```
There is much more to `Kino` and we have [a series of Kino guides
in the Explore section to teach you more](/explore).
Note that it is a good idea to specify versions of the installed packages,
so that the notebook is easily reproducible later on. The install
command goes beyond simply installing dependencies, it also caches
them, consolidates protocols, and more. Check
[its documentation](https://hexdocs.pm/mix/Mix.html#install/2)
to learn more.
Finally, keep in mind that `Mix.install/2` can be called only once
per runtime, so if you need to modify the dependencies, you should
go to the notebook runtime configuration and **reconnect** the current
runtime. Let's learn how to do that.
## Runtimes
Livebook has a concept of **runtime**, which in practice is an Elixir node responsible
for evaluating your code. You can choose the runtime by clicking the "Runtime" icon
(<i class="ri-livebook-runtime"></i>) on the sidebar (or by using the <kbd>s</kbd> <kbd>r</kbd>
keyboard shortcut).
By default, a new Elixir node is started (similarly to starting `iex`). You
can click reconnect whenever you want to discard the current node and start
a new one.
You can also choose to run inside a *Mix* project (as you would with `iex -S mix`),
manually *attach* to an existing distributed node, or run your Elixir notebook
*embedded* within the Livebook source itself.
## More on branches #1
We already mentioned branching sections in
[Welcome to Livebook](/explore/notebooks/intro-to-livebook),
but in Elixir terms each branching section:
* runs in a separate process from the main flow
* copies relevant bindings, imports and alises from the parent
* updates its process dictionary to mirror the parent
Let's see this in practice:
```elixir
parent = self()
```
```elixir
Process.put(:info, "deal carefully with process dictionaries")
```
<!-- livebook:{"branch_parent_index":5} -->
## More on branches #2
```elixir
parent
```
```elixir
self()
```
```elixir
Process.get(:info)
```
Since this branch is a separate process, a crash has limited scope:
```elixir
Process.exit(self(), :kill)
```
## Evaluation vs compilation
Livebook automatically shows the execution time of each Elixir
cell on the bottom-right of the cell. After evaluation, the total
time can be seen by hovering the green dot.
However, it is important to remember that all code outside of
a module in Elixir is *evaluated*, and therefore executes much
slower than code defined inside modules, which are *compiled*.
Let's see an example. Run the cell below:
```elixir
Enum.reduce(1..1_000_000, 0, fn x, acc -> x + acc end)
```
We are adding all of the elements in a range by iterating them
one by one. However, executing it likely takes some reasonable
amount of time, as the invocation of the `Enum.reduce/3` as well
as the anonymous function argument are evaluated.
However, what if we move the above to inside a function? Let's do
that:
```elixir
defmodule Bench do
def sum do
Enum.reduce(1..1_000_000, 0, fn x, acc -> x + acc end)
end
end
```
Now let's try running it:
```elixir
Bench.sum()
```
The latest cell should execute orders of magnitude faster than
the previous `Enum.reduce/3` call. While the call `Bench.sum()`
itself is evaluated, the one million iterations of `Enum.reduce/3`
happen inside a module, which is compiled.
If a notebook is performing slower than expected, consider moving
the bulk of the execution to inside modules.
## Running tests
It is also possible to run tests directly from your notebooks.
The key is to disable `ExUnit`'s autorun feature and then explicitly
run the test suite after all test cases have been defined:
```elixir
ExUnit.start(autorun: false)
defmodule MyTest do
use ExUnit.Case, async: true
test "it works" do
assert true
end
end
ExUnit.run()
```
This helps you follow best practices and ensure the code you write
behaves as expected!