Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MEP 0003: UI Elements #3

Merged
merged 19 commits into from
Nov 16, 2022
Prev Previous commit
Next Next commit
iterating ...
  • Loading branch information
akshayka committed Nov 10, 2022
commit eb0d2975401172fbaa60c1288443701c1229d6d1
245 changes: 206 additions & 39 deletions mep-0003.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ provides a library of pre-fabricated UI elements that programmers
can use in their apps. The main concerns of the design are:

- a UI element as an HTML element with a value
- synchronizing a UI element's value in the frontend with its value in the
kernel
- syntax for accessing a UI element's value
- syncronizing a UI element's appearance when it is shown multiple times
within an app
- syntax for accessing a UI element's value
- extensibility, or how to make custom elements
- synchronizing a UI element's value in the frontend with its value in the
kernel
- a simple rule that determines which cells are run when a UI element is interacted with
- a simple mechanism for making custom elements

## Motivation

Expand All @@ -43,15 +44,14 @@ is carefully designed.
We strive for a design that satisfies the following criteria:

- *seamless*: accessing a UI element's value must be seamless; no callbacks
- *simple*: a simple rule should explain what cells are run when a UI element is interacted with
- *Pythonic*: creating, displaying, and reading UI elements should be Pythonic
- *no web programming*: 99 percent of users shouldn't need to write any HTML,
- *no web programming*: 99% of users shouldn't need to write any HTML,
Javascript, or CSS
- *customizable*: styling of UI elements should be customizable with CSS
- *extensible*: it should be easy for developers to implement custom UI
elements, using web technologies of their choice

A big plus, but not a requirement for now, is the following:
- *customizable*: the styling of UI elements should be customizable

## UI elements: HTML objects with values
A UI element is an HTML element that has a _value_.
Its value may change when interacted with.
Expand All @@ -68,39 +68,166 @@ A `UIElement` is an `HTML` element that has a `value` attribute. This value can
be set at creation time by the user, to designate a default value for the
element; after creation, `value` cannot be changed by Python code.

The frontend can render the same `UIElement` multiple times, with multiple
instances of the same element tied together via a unique key. These rendered
elements may choose to update their values at any time, but typically their
values will only change upon user interaction.

**Frontend synchronization.** When the value of a rendered `UIElement` changes,
all other instances of the element that are rendered in the frontend have their
value updated as well. This ensures that the frontend remains consistent.

**Kernel synchronization**. Interacting with a rendered `UIElement` in the
frontend may change the `value` attribute on the Python object; when the value
is updated, every cell referencing the `UIElement` object is run reactively,
_except_ the cell that created the `UIElement`.
This enables interactivity to drive execution of other cells.
**Kernel synchronization**.
When a rendered `UIElement`s value changes, the frontend sends a value update
request to the kernel; the kernel is then responsible for updating the `value`
attribute on the Python object corresponding to the element. When this
happens, the kernel wil run certain cells that depend on the `UIElement`
object, enbling user interaction to drive execution of other cells.
The rule for which cells are run is described in the next section.

## Runtime Ruleset

**1. An interaction will never cause a cell that creates the interacted-with
`UIElement` to run.**

_Why not run the creating cell?_ If the cell that created the `UIElement` were
run upon interaction, then the `UIElement` would be reconstructed
and re-initialized with its default value, which would effectively
undo the effect of interacting with the element.
When a `UIElement` is interacted with in the frontend,
the cell that created it will _never_ be re-run, even if the interaction was
tied to the creating cell.

This can lead to confusing behavior: for example, consider the following cell
_Why we have this rule_.
If a cell that created the `UIElement` were run upon interaction, then the
`UIElement` would be reconstructed and re-initialized with its default value,
which would effectively undo the effect of interacting with the element.

_A corner case_.
Under this rule, in the following two cells,

> cell a
```python
# this cell won't do what its author wanted it to do ...
slider = mo.ui.slider(start=1, stop=10)
x = slider.value
```

> cell b
```python
slider
```

Interacting with the emitted slider will _not_ update the value of `x`, since
this cell creates `slider` and therefore it won't be triggered by the
interaction.
interacting with the emitted slider will _not_ update the value of `x`. We make
it invalid to write these kinds of cells by imposing the following rule.

**2. The value of a `UIElement` cannot be accessed in its creating cell.**
We raise an exception at runtime when a `UIElement`'s value is accessed in the
cell that created it. For example, the second line of this cell

```python
slider = mo.ui.slider(start=1, stop=10)
x = slider.value
slider
```

will raise an exception.

XXX in just creating
cells, or all cells? what are legitimate use cases for reading the value and
outputing in the same cell? would you ever condition outputing the element on
its value? perhaps if its value is > some number, then you output another
element in addition to the current element?

**3. XXX**

### Examples

**A simple example.**

> cell a:
```python
text = mo.ui.text()
text
```

>cell b:
```python
text.value
```

Interacting with the output of cell `a` will cause cell `b` to run, because XXX

**A cell that emits an output.**

```python
> cell a:
```python
text = mo.ui.text()
text
```

>cell b:
```python
text
```

Interacting with the output of `b` will update `text.value` in the kernel, but
it will not cause any cells to be run, because XXX. In particular, `cell b`
will not b rerun.


**A cell that emits an output and references its value.**


> cell a
```python
text = mo.ui.text()
```

> cell b:
```python
contents = text.value
text, contents
```

>cell c:
```python
contents
```

Interacting with `text` (the ouptut of `cell b`) will cause `cell b` to be run,
because XXX, which in turn will cause `cell c` to be run because it has
`contents`, a def of `b`, is among its refs.

**A cell that creates an output, accesses its value, and emits the output.**

```python
text = mo.ui.text()
contents = text.value
text
```

_Disallow accessing `value` before a UIElement is shown._ To mitigate this
confusing behavior, we will raise an exception when a `UIElement`s value is
accessed before the element is shown on the page.
Line 2 will raise an exception at runtime, because the value of `text` is
accessed in the cell in which `text` is constructed.

### Input elements
**Unnamed UI elements.**

> cell a
```python
l = [mo.ui.text()]
l
```

> cell b
```python
l[0].value
```

Interacting with `l`'s stored text element in `cell a` will _not_ cause `cell
b` to run, because the text element is unnamed: `l[0]` is not a name bound to a
`UIElement` object, instead it is an expression that evaluates to a `UIElement`
at runtime.



## Library of Included Elements

An important subclass of UI elements are the [HTML input
elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
Expand All @@ -113,7 +240,7 @@ that stores its current value; some have special attributes (such as 'checked'
for checkboxes) that contain additional information. An input event is fired
whenever the value is changed.

#### Library of included elements
#### Input elements

**Slider.** A numeric slider that displays the currently selected number.

Expand Down Expand Up @@ -152,18 +279,6 @@ code. Our design must have also support these other elements.
XXX: implement in separate .js/.ts file, package into a library? write raw
HTML/javascript in python?

## Examples


```python
slider = mo.ui.slider(start=1, stop=10, step=1)
slider
```

```python
slider.value
```


## Implementation

Expand Down Expand Up @@ -247,7 +362,59 @@ XXX

## Alternatives considered

**`bind` methods.**
<table>
<th> Rerun cell if ... </th>
<th>UIElement's name is a ref</th>
<th>UIElement's value attribute is referenced</th>

<tr>
<td></td>
<td>Reruns the interacted with cell</td>
<td>Does not rerun the interacted with cell</td>
</tr>

<tr>
<td></td>
<td>Runs all cells that have name as a ref, even if they don't access its value.</td>
<td>Only runs cell if they access element's value.</td>
</tr>

<tr>
<td></td>
<td>Compatible with accessing UIElement's value without referencing the `value`
attr, such as via a cast or operator overload </td>
<td>Incompatible with accessing UIElement's value without referencing the
`value` attr.</td>
</tr>

<tr>
<td></td>
<td>Compatible with pointer traversal to discover unnamed UI elements.</td>
<td>Incompatible with pointer traversal to discover unnamed UI elements.</td>
</tr>

<tr>
<td></td>
<td>Easier to explain.</td>
<td>Easy to explain.</td>
</tr>
</table>

The only cost to relying on the name, instead of the value attribute, is
running cells that may not have needed to be rerun, especially including the
cell that was just interacted with. This can be a performance consideration
(which is okay), but it can also be a correctness consideration if the cell is
not idempotent, and was not meant to be rerun. However, in marimo, it is best
practice to make all cells idempotent --- you should be prepared for your
cells to be rerun at any time. If the refs are the same, you likely want to
the defs and the output to be the same.


**Never re-running the cell whose output was interacted with.**

**Traversing pointers to find unnamed UI elements.**

**Manually binding names to UI elements.**

## XXX Scratch

Expand Down