Initial revamp of notebooks

Deployment and introduction to Kino
still need to be rewritten.
This commit is contained in:
José Valim 2023-03-14 15:28:52 +01:00
parent 26af7c33ce
commit 764d775ed3
7 changed files with 240 additions and 257 deletions

View file

@ -54,21 +54,21 @@ defmodule Livebook.Notebook.Learn do
details: %{
description:
"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"
}
},
%{
path: Path.join(__DIR__, "learn/intro_to_kino.livemd"),
path: Path.join(__DIR__, "learn/deploy_apps.livemd"),
details: %{
description: "Make your notebooks interactive with inputs, controls, and more.",
cover_url: "/images/kino.png"
description: "Write and deploy a chat app with Kino control and frames.",
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"),
details: %{
description: "Intuitive data visualizations and data pipelines on the fly.",
cover_url: "/images/explorer.png"
}
ref: :kino_reference,
path: Path.join(__DIR__, "learn/kino/reference.livemd")
},
%{
ref: :kino_vm_introspection,
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,
path: Path.join(__DIR__, "learn/kino/pong.livemd")
@ -207,25 +200,17 @@ defmodule Livebook.Notebook.Learn do
@group_configs [
%{
title: "Advanced Kino",
title: "Deep dive into Kino",
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",
notebook_refs: [
:kino_reference,
:kino_vm_introspection,
:kino_custom_kinos,
:kino_pong,
: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
]
}
]

View file

@ -1,4 +1,4 @@
# Building a chat app with Kino.Control
# Deploy a chat app with Kino
```elixir
Mix.install([

View file

@ -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>&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.
## 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!

View file

@ -24,9 +24,12 @@ Subsequent cells have access to the bindings you've defined:
String.replace(message, "🍵", "☕")
```
Note however that bindings are not global, so each cell *sees* only stuff that goes
above itself. This approach helps to keep the notebook clean and predictable
as you keep working on it!
Note however that bindings are not global, so each cell *sees* only stuff
that goes above itself. This approach helps to keep the notebook clean and
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
@ -72,7 +75,9 @@ Process.sleep(300_000)
```
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
@ -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>)
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,
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.
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
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
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
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
to execute the linked notebook.
<!-- livebook:{"break_markdown":true} -->
### Math expressions
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).
<!-- livebook:{"break_markdown":true} -->
### Mermaid diagrams
[Mermaid](https://mermaid-js.github.io/) is a library for creating diagrams
@ -138,6 +213,142 @@ graph TD;
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
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)
notebook;
* Learn how Elixir integrates with Livebook in the
[Elixir and Livebook](/learn/notebooks/elixir-and-livebook) notebook;
* Go back [to the Learn page](/learn) and see how to use Livebook to
deploy apps, explore data, plot graphs, and much more;
* Finally, remember Livebook is an open source project, so feel free to
look into [the repository](https://github.com/livebook-dev/livebook)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

@ -25,7 +25,11 @@ defmodule Livebook.StorageTest do
}} = Storage.fetch(:insert, "replace")
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,
%{