mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-11 06:32:23 +08:00
Initial revamp of notebooks
Deployment and introduction to Kino still need to be rewritten.
This commit is contained in:
parent
26af7c33ce
commit
764d775ed3
7 changed files with 240 additions and 257 deletions
|
|
@ -54,21 +54,21 @@ defmodule Livebook.Notebook.Learn do
|
||||||
details: %{
|
details: %{
|
||||||
description:
|
description:
|
||||||
"A fast-paced introduction to Elixir by building distributed data-transfer portals.",
|
"A fast-paced introduction to Elixir by building distributed data-transfer portals.",
|
||||||
cover_url: "/images/elixir-portal.jpeg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
path: Path.join(__DIR__, "learn/elixir_and_livebook.livemd"),
|
|
||||||
details: %{
|
|
||||||
description: "Learn how to use some of their unique features together.",
|
|
||||||
cover_url: "/images/elixir.png"
|
cover_url: "/images/elixir.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
path: Path.join(__DIR__, "learn/intro_to_kino.livemd"),
|
path: Path.join(__DIR__, "learn/deploy_apps.livemd"),
|
||||||
details: %{
|
details: %{
|
||||||
description: "Make your notebooks interactive with inputs, controls, and more.",
|
description: "Write and deploy a chat app with Kino control and frames.",
|
||||||
cover_url: "/images/kino.png"
|
cover_url: "/images/learn-deploy.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
path: Path.join(__DIR__, "learn/intro_to_explorer.livemd"),
|
||||||
|
details: %{
|
||||||
|
description: "Intuitive data visualizations and data pipelines on the fly.",
|
||||||
|
cover_url: "/images/explorer.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
|
@ -86,20 +86,13 @@ defmodule Livebook.Notebook.Learn do
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
path: Path.join(__DIR__, "learn/intro_to_explorer.livemd"),
|
ref: :kino_reference,
|
||||||
details: %{
|
path: Path.join(__DIR__, "learn/kino/reference.livemd")
|
||||||
description: "Intuitive data visualizations and data pipelines on the fly.",
|
|
||||||
cover_url: "/images/explorer.png"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
ref: :kino_vm_introspection,
|
ref: :kino_vm_introspection,
|
||||||
path: Path.join(__DIR__, "learn/kino/vm_introspection.livemd")
|
path: Path.join(__DIR__, "learn/kino/vm_introspection.livemd")
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
ref: :kino_chat_app,
|
|
||||||
path: Path.join(__DIR__, "learn/kino/chat_app.livemd")
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
ref: :kino_pong,
|
ref: :kino_pong,
|
||||||
path: Path.join(__DIR__, "learn/kino/pong.livemd")
|
path: Path.join(__DIR__, "learn/kino/pong.livemd")
|
||||||
|
|
@ -207,25 +200,17 @@ defmodule Livebook.Notebook.Learn do
|
||||||
|
|
||||||
@group_configs [
|
@group_configs [
|
||||||
%{
|
%{
|
||||||
title: "Advanced Kino",
|
title: "Deep dive into Kino",
|
||||||
description:
|
description:
|
||||||
"Advanced guides for learning more about the Kino package, including the creation of custom UI components.",
|
"Learn more about the Kino package, including the creation of custom UI components.",
|
||||||
cover_url: "/images/kino.png",
|
cover_url: "/images/kino.png",
|
||||||
notebook_refs: [
|
notebook_refs: [
|
||||||
|
:kino_reference,
|
||||||
:kino_vm_introspection,
|
:kino_vm_introspection,
|
||||||
:kino_custom_kinos,
|
:kino_custom_kinos,
|
||||||
:kino_pong,
|
:kino_pong,
|
||||||
:kino_smart_cells
|
:kino_smart_cells
|
||||||
]
|
]
|
||||||
},
|
|
||||||
%{
|
|
||||||
title: "Building and deploying apps",
|
|
||||||
description:
|
|
||||||
"Advanced guides for learning more about the deploying experience and teaching Kino",
|
|
||||||
cover_url: "/images/kino.png",
|
|
||||||
notebook_refs: [
|
|
||||||
:kino_chat_app
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Building a chat app with Kino.Control
|
# Deploy a chat app with Kino
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
Mix.install([
|
Mix.install([
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
# 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](/learn/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> + <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.
|
|
||||||
|
|
||||||
## Getting the current directory
|
|
||||||
|
|
||||||
You can access the location of the current `.livemd` file and
|
|
||||||
its current directory using `__ENV__` and `__DIR__` variables:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
IO.puts(__ENV__.file)
|
|
||||||
IO.puts(__DIR__)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mix projects
|
|
||||||
|
|
||||||
Sometimes you may want to run a notebook within the context of an existing
|
|
||||||
Mix project. This is possible from Elixir v1.14 with the help of `Mix.install/2`.
|
|
||||||
|
|
||||||
As an example, imagine you have created a notebook inside your current project,
|
|
||||||
at `notebooks/example.livemd`. In order to run within the root Mix project, using
|
|
||||||
the same configuration and dependencies versions, you can change your notebook
|
|
||||||
setup cell to invoke `Mix.install/2` with the following arguments:
|
|
||||||
|
|
||||||
<!-- livebook:{"force_markdown":true} -->
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
my_app_root = Path.join(__DIR__, "..")
|
|
||||||
|
|
||||||
Mix.install(
|
|
||||||
[
|
|
||||||
{:my_app, path: my_app_root, env: :dev}
|
|
||||||
],
|
|
||||||
config_path: Path.join(my_app_root, "config/config.exs"),
|
|
||||||
lockfile: Path.join(my_app_root, "mix.lock")
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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 manually *attach* to an existing distributed node by picking the
|
|
||||||
"Attached Node" runtime. To do so, you will need the Erlang Name of the external node
|
|
||||||
and its Erlang Cookie. For example, you can start a Phoenix application as follows:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ iex --sname phoenix-app --cookie secret -S mix phx.server
|
|
||||||
```
|
|
||||||
|
|
||||||
Now open up a new notebook and click the "Runtime" icon on the sidebar.
|
|
||||||
Click to "Configure" the runtime and choose "Attached node". Input the
|
|
||||||
name and cookie as above and you should be ready to connect to it.
|
|
||||||
|
|
||||||
Note, however, that you can't install new dependencies on a connected runtime.
|
|
||||||
If you want to install dependencies, you have two options:
|
|
||||||
|
|
||||||
1. Use the Mix project approach outlined in the previous section;
|
|
||||||
|
|
||||||
2. Use a regular notebook and use `Node.connect/1`](https://hexdocs.pm/elixir/Node.html#connect/1)
|
|
||||||
to connect to your application. Use [the `:erpc` module](https://www.erlang.org/doc/man/erpc.html)
|
|
||||||
to fetch data from the remote node and execute code.
|
|
||||||
|
|
||||||
## More on branches #1
|
|
||||||
|
|
||||||
We already mentioned branching sections in
|
|
||||||
[Welcome to Livebook](/learn/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 aliases 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":4} -->
|
|
||||||
|
|
||||||
## 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 Code
|
|
||||||
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!
|
|
||||||
|
|
@ -24,9 +24,12 @@ Subsequent cells have access to the bindings you've defined:
|
||||||
String.replace(message, "🍵", "☕")
|
String.replace(message, "🍵", "☕")
|
||||||
```
|
```
|
||||||
|
|
||||||
Note however that bindings are not global, so each cell *sees* only stuff that goes
|
Note however that bindings are not global, so each cell *sees* only stuff
|
||||||
above itself. This approach helps to keep the notebook clean and predictable
|
that goes above itself. This approach helps to keep the notebook clean and
|
||||||
as you keep working on it!
|
predictable as you keep working on it. Furthermore, Livebook tracks which
|
||||||
|
variables are used by each cell and in order to detect which cells become
|
||||||
|
stale. For example, try changing the `message` variable and you will see
|
||||||
|
the status indicator on the bottom right of the second cell become yellow.
|
||||||
|
|
||||||
## Sections
|
## Sections
|
||||||
|
|
||||||
|
|
@ -72,7 +75,9 @@ Process.sleep(300_000)
|
||||||
```
|
```
|
||||||
|
|
||||||
Having this cell running, feel free to insert another Code cell
|
Having this cell running, feel free to insert another Code cell
|
||||||
in the section below and see it evaluates immediately.
|
in the section below and see it evaluates immediately. Crashes
|
||||||
|
in branched sections also have limited scope and will affect only
|
||||||
|
the branched section.
|
||||||
|
|
||||||
## Saving notebooks
|
## Saving notebooks
|
||||||
|
|
||||||
|
|
@ -81,12 +86,20 @@ interactive hacking, but oftentimes you will want to save your work for later.
|
||||||
Such can be done by clicking on the "Disk" icon (<i class="ri-livebook-save"></i>)
|
Such can be done by clicking on the "Disk" icon (<i class="ri-livebook-save"></i>)
|
||||||
in the bottom-right corner and selecting the file location.
|
in the bottom-right corner and selecting the file location.
|
||||||
|
|
||||||
|
Once saved, you can access the location of the current `.livemd` file and
|
||||||
|
its current directory using `__ENV__` and `__DIR__` variables:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
IO.puts(__ENV__.file)
|
||||||
|
IO.puts(__DIR__)
|
||||||
|
```
|
||||||
|
|
||||||
Notebooks are stored in **live markdown** format, which is the Markdown you know,
|
Notebooks are stored in **live markdown** format, which is the Markdown you know,
|
||||||
with just a few assumptions on how particular elements are represented. Thanks to this
|
with just a few assumptions on how particular elements are represented. Thanks to this
|
||||||
approach you can easily keep notebooks under version control and get readable diffs.
|
approach you can easily keep notebooks under version control and get readable diffs.
|
||||||
You can also easily preview those files and even edit them in a text editor.
|
You can also easily preview those files and even edit them in a text editor.
|
||||||
|
|
||||||
## Stepping up your workflow
|
## Keyboard shortcuts
|
||||||
|
|
||||||
Once you start using notebooks more, it's gonna be beneficial
|
Once you start using notebooks more, it's gonna be beneficial
|
||||||
to optimise how you move around. Livebook leverages the concept of
|
to optimise how you move around. Livebook leverages the concept of
|
||||||
|
|
@ -95,6 +108,64 @@ Make sure to check out the shortcuts by clicking the "Keyboard" icon
|
||||||
(<i class="ri-livebook-shortcuts"></i>) in the sidebar or
|
(<i class="ri-livebook-shortcuts"></i>) in the sidebar or
|
||||||
by pressing <kbd>?</kbd>.
|
by pressing <kbd>?</kbd>.
|
||||||
|
|
||||||
|
<!-- livebook:{"branch_parent_index":1} -->
|
||||||
|
|
||||||
|
## Autocompletion
|
||||||
|
|
||||||
|
Elixir code cells also support autocompletion. Autocompletion
|
||||||
|
happens automatically as you type but you can explicitly trigger
|
||||||
|
it with <kbd>ctrl</kbd> + <kbd>␣</kbd>. You must have started
|
||||||
|
executed a cell at least once for the autocompletion engine to
|
||||||
|
load.
|
||||||
|
|
||||||
|
Let's try autocompleting the code below to `System.version()`.
|
||||||
|
First put the cursor after the `System` below and type `.`:
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
<!-- livebook:{"branch_parent_index":1} -->
|
||||||
|
|
||||||
|
## Storing secrets in Hubs
|
||||||
|
|
||||||
|
Livebook is capable of managing all secrets that belong to a notebook.
|
||||||
|
Secrets can be read by Elixir Code cells and also by Smart cells -
|
||||||
|
a feature we will explore in future sections.
|
||||||
|
|
||||||
|
Secrets are environment variables starting with the `LB_` prefix. For
|
||||||
|
example, let's try reading an environment variable:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
System.fetch_env!("LB_MY_SECRETZ")
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute the cell above. If the secret is not available, Livebook will
|
||||||
|
automatically detect it and prompt you to add the missing secret. You
|
||||||
|
can save a secret in two different locations:
|
||||||
|
|
||||||
|
* in your notebook session: once you restart the session or your
|
||||||
|
Livebook, the secret will be gone and you must input it again
|
||||||
|
|
||||||
|
* in a Hub: a Hub is a location where you can safely store secrets,
|
||||||
|
deploy notebooks, and more. Every Livebook application has a personal
|
||||||
|
Hub that can store secrets and stamp notebooks
|
||||||
|
|
||||||
|
Once the secret is saved, execute the cell again and it will evaluate
|
||||||
|
successfully.
|
||||||
|
|
||||||
|
If you save secrets in your Hub, you will only need to input the secret
|
||||||
|
once in notebooks authored by you. You can manage all of your secrets
|
||||||
|
by clicking the lock icon (<i class="ri-lock-password-line"></i>) on the
|
||||||
|
sidebar.
|
||||||
|
|
||||||
## Markdown extensions
|
## Markdown extensions
|
||||||
|
|
||||||
Livebook also include supports for links, mathematical expressions, and Mermaid diagrams.
|
Livebook also include supports for links, mathematical expressions, and Mermaid diagrams.
|
||||||
|
|
@ -107,6 +178,8 @@ to a Livebook named `chapter_2.livemd` in the same directory as the current
|
||||||
notebook. Once clicked, Livebook will automatically open up a new session
|
notebook. Once clicked, Livebook will automatically open up a new session
|
||||||
to execute the linked notebook.
|
to execute the linked notebook.
|
||||||
|
|
||||||
|
<!-- livebook:{"break_markdown":true} -->
|
||||||
|
|
||||||
### Math expressions
|
### Math expressions
|
||||||
|
|
||||||
Livebook uses $\TeX$ syntax for math inside your Markdown cells.
|
Livebook uses $\TeX$ syntax for math inside your Markdown cells.
|
||||||
|
|
@ -124,6 +197,8 @@ how they are written.
|
||||||
|
|
||||||
You can explore all supported expressions in the [KaTeX documentation](https://katex.org/docs/supported.html).
|
You can explore all supported expressions in the [KaTeX documentation](https://katex.org/docs/supported.html).
|
||||||
|
|
||||||
|
<!-- livebook:{"break_markdown":true} -->
|
||||||
|
|
||||||
### Mermaid diagrams
|
### Mermaid diagrams
|
||||||
|
|
||||||
[Mermaid](https://mermaid-js.github.io/) is a library for creating diagrams
|
[Mermaid](https://mermaid-js.github.io/) is a library for creating diagrams
|
||||||
|
|
@ -138,6 +213,142 @@ graph TD;
|
||||||
C-->D;
|
C-->D;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Elixir integration
|
||||||
|
|
||||||
|
Here are some tips on how to better integrate Livebook with Elixir.
|
||||||
|
|
||||||
|
### Mix projects
|
||||||
|
|
||||||
|
Sometimes you may want to run a notebook within the context of an existing
|
||||||
|
Mix project. This is possible from Elixir v1.14 with the help of `Mix.install/2`.
|
||||||
|
|
||||||
|
As an example, imagine you have created a notebook inside your current project,
|
||||||
|
at `notebooks/example.livemd`. In order to run within the root Mix project, using
|
||||||
|
the same configuration and dependencies versions, you can change your notebook
|
||||||
|
setup cell to invoke `Mix.install/2` with the following arguments:
|
||||||
|
|
||||||
|
<!-- livebook:{"force_markdown":true} -->
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
my_app_root = Path.join(__DIR__, "..")
|
||||||
|
|
||||||
|
Mix.install(
|
||||||
|
[
|
||||||
|
{:my_app, path: my_app_root, env: :dev}
|
||||||
|
],
|
||||||
|
config_path: Path.join(my_app_root, "config/config.exs"),
|
||||||
|
lockfile: Path.join(my_app_root, "mix.lock")
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- livebook:{"break_markdown":true} -->
|
||||||
|
|
||||||
|
### 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 manually *attach* to an existing distributed node by picking the
|
||||||
|
"Attached Node" runtime. To do so, you will need the Erlang Name of the external node
|
||||||
|
and its Erlang Cookie. For example, you can start a Phoenix application as follows:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ iex --sname phoenix-app --cookie secret -S mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
Now open up a new notebook and click the "Runtime" icon on the sidebar.
|
||||||
|
Click to "Configure" the runtime and choose "Attached node". Input the
|
||||||
|
name and cookie as above and you should be ready to connect to it.
|
||||||
|
|
||||||
|
Note, however, that you can't install new dependencies on a connected runtime.
|
||||||
|
If you want to install dependencies, you have two options:
|
||||||
|
|
||||||
|
1. Use the Mix project approach outlined in the previous section;
|
||||||
|
|
||||||
|
2. Use a regular notebook and use
|
||||||
|
[`Node.connect/1`](https://hexdocs.pm/elixir/Node.html#connect/1)
|
||||||
|
to connect to your application.
|
||||||
|
Use [the `:erpc` module](https://www.erlang.org/doc/man/erpc.html)
|
||||||
|
to fetch data from the remote node and execute code.
|
||||||
|
|
||||||
|
<!-- livebook:{"break_markdown":true} -->
|
||||||
|
|
||||||
|
### Evaluation vs compilation
|
||||||
|
|
||||||
|
Livebook automatically shows the execution time of each Code
|
||||||
|
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 a least an order 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.
|
||||||
|
|
||||||
|
<!-- livebook:{"break_markdown":true} -->
|
||||||
|
|
||||||
|
### 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!
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
That's our quick intro to Livebook! Where to go next?
|
That's our quick intro to Livebook! Where to go next?
|
||||||
|
|
@ -147,8 +358,8 @@ That's our quick intro to Livebook! Where to go next?
|
||||||
with Elixir](/learn/notebooks/distributed-portals-with-elixir)
|
with Elixir](/learn/notebooks/distributed-portals-with-elixir)
|
||||||
notebook;
|
notebook;
|
||||||
|
|
||||||
* Learn how Elixir integrates with Livebook in the
|
* Go back [to the Learn page](/learn) and see how to use Livebook to
|
||||||
[Elixir and Livebook](/learn/notebooks/elixir-and-livebook) notebook;
|
deploy apps, explore data, plot graphs, and much more;
|
||||||
|
|
||||||
* Finally, remember Livebook is an open source project, so feel free to
|
* Finally, remember Livebook is an open source project, so feel free to
|
||||||
look into [the repository](https://github.com/livebook-dev/livebook)
|
look into [the repository](https://github.com/livebook-dev/livebook)
|
||||||
|
|
|
||||||
1
static/images/learn-deploy.svg
Normal file
1
static/images/learn-deploy.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
|
|
@ -25,7 +25,11 @@ defmodule Livebook.StorageTest do
|
||||||
}} = Storage.fetch(:insert, "replace")
|
}} = Storage.fetch(:insert, "replace")
|
||||||
|
|
||||||
assert :ok =
|
assert :ok =
|
||||||
Storage.insert(:insert, "replace", key1: "updated_val1", key2: "val2", key3: "val3")
|
Storage.insert(:insert, "replace",
|
||||||
|
key1: "updated_val1",
|
||||||
|
key2: "val2",
|
||||||
|
key3: "val3"
|
||||||
|
)
|
||||||
|
|
||||||
assert {:ok,
|
assert {:ok,
|
||||||
%{
|
%{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue