Skip to content

Commit

Permalink
[docs] Integrate & polish FSM/Dataflow theory
Browse files Browse the repository at this point in the history
  • Loading branch information
kimo-k committed Nov 21, 2023
1 parent b374fee commit 36b832e
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 39 deletions.
62 changes: 29 additions & 33 deletions docs/Flows.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ And, we use this subscription in a view:
- `reg-flow` - creates a running node from a specification

Crucially, the name `flow` isn't exactly short for "dataflow".
A `flow` is a static value, specifying one segment of a dataflow graph.
Dataflow is a dynamic phenomenon, not a value.
A `flow` is a static value, specifying one possible segment of a dataflow graph.
Dataflow is a [dynamic process](/re-frame/on-dynamics/#on-dynamics), not a value.
Both the data and the graph itself change over time.
Changing the graph is a matter of [registering and clearing](#redefining-and-undefining) flows.

Expand Down Expand Up @@ -226,7 +226,7 @@ You might notice a similarity with [reagent.core/reaction](https://reagent-proje
Both yield an "automatically" changing value.

Reagent controls *when* a reaction updates, presumably during the evaluation of a component function.
Flows, on the other hand, are controlled by re-frame, running every time an `event` occurs.
Flows, on the other hand, are part of [re-frame time](/re-frame/on-dynamics/#re-frame-time), running every time an `event` occurs.

When a component derefs a reaction, that component knows to re-render when the value changes.

Expand Down Expand Up @@ -271,7 +271,7 @@ Just like a `flow`, this subscription's value changes whenever the inputs change

A flow stores its `:output` value in `app-db`, while subscriptions don't. We designed re-frame on the premise that `app-db` holds your *entire* app-state.
Arguably, derived values belong there too. We feel there's an inherent reasonability to storing everything in one place.
It's also more practical (see [Reactive Context](/re-frame/flows-advanced-topics#reactive-context).
It's also more practical (see [Reactive Context](/re-frame/flows-advanced-topics#reactive-context)).

Just like with layered subscriptions, one flow can use the value of another. Remember the `:inputs` to our first flow?

Expand All @@ -280,27 +280,9 @@ Just like with layered subscriptions, one flow can use the value of another. Rem
:h [:garage :length]}}
</div>

## Subscribing to flows

In our examples so far, we've used a regular subscription, getting our flow's output path.
In `re-frame.alpha`, you can also subscribe to a flow by name.
This bypasses the [caching behavior](/re-frame/flows-advanced-topics#caching) of a standard subscription.

Here's how you can subscribe to our garage-area flow.
The stable way, with a query vector:

```
(re-frame.alpha/subscribe [:flow {:id :garage-area}])`
```

And the experimental way, with a query map:
```
(re-frame.alpha/sub :flow {:id :garage-area})
```

## Layering flows

As you can see, vectors stand for paths in `app-db`.
In the values of the `:inputs` map, vectors stand for paths in `app-db`.
The `flow<-` function, however, gives us access to *other flows*.

Here's a flow using two other flows as inputs: `::kitchen-area` and `::living-room-area`.
Expand All @@ -315,13 +297,27 @@ When either input changes value, our flow calls the `:output` function to recalc
:path [:ratios :main-rooms]}
</div>

As before, once `:output` runs, the resulting value is stored at `:path`. So, the new value of `app-db` will contain a number at the path `[:ratios :main-rooms]`
As before, once `:output` runs, the resulting value is stored at `:path`.
So, the new value of `app-db` will contain a number at the path `[:ratios :main-rooms]`

## Subscribing to flows

In our examples so far, we've used a regular subscription, getting our flow's output path.
In `re-frame.alpha`, you can also subscribe to a flow by name.
This bypasses the [caching behavior](/re-frame/flows-advanced-topics#caching) of a standard subscription.

Here's how you can subscribe to our garage-area flow.
The stable way, with a query vector:

<div class="cm-doc" data-cm-doc-no-eval data-cm-doc-no-edit data-cm-doc-no-result>
(re-frame.alpha/subscribe [:flow {:id :garage-area}])
</div>

And the experimental way, with a query map:

For subscriptions, caching can be an issue (see [caching](#caching)). With flows, the process is simpler.
`app-db` *is* the cache, since flows always store their output value within it.
You, the programmer, define explicitly when to recalculate the output, *and* when to store the output.
To this end, flows provide optional keys: `:live?` and `:cleanup`.
Let's read on, and discover how these keys work together to fully define the lifecycle and caching behavior of a flow:
<div class="cm-doc" data-cm-doc-no-eval data-cm-doc-no-edit data-cm-doc-no-result>
(re-frame.alpha/sub :flow {:id :garage-area})
</div>

## Living and Dying

Expand All @@ -335,7 +331,7 @@ Sometimes we'd like to simply turn our flow off, so we can stop thinking about i
For this, we use a `:live?` function.

The quote above deals with phenomenal life, but you can also think of `:live?` as in a tv or internet broadcast.
Data flows, but only when the flow itself is live.
Data flows, but only when the `flow` itself is live.

Let's try it out. For example, here's a barebones tab picker, and something to show us the value of `app-db`:

Expand Down Expand Up @@ -509,7 +505,7 @@ Then, we'll use a flow to evaluate which requirements are met.
Our flow doesn't care how it happened that a requirement was met, nor what to do next.

For reasons that will become clear, let's write a [factory function](https://en.wikipedia.org/wiki/Factory_%28object-oriented_programming%29) for this flow.
It builds a flow that validates our item list against the requirements:
It builds a flow that validates our item list against any given requirements:

<div class="cm-doc" data-cm-doc-result-format="pass-fail">
(defn error-state-flow [{:keys [min-items max-items] :as requirements}]
Expand All @@ -524,14 +520,14 @@ It builds a flow that validates our item list against the requirements:
:else :ok)))})
</div>

And register a flow that fits our base requirements:
And let's register a flow that fits our base requirements:

<div class="cm-doc" data-cm-doc-result-format="pass-fail">
(rf/reg-flow (error-state-flow base-requirements))
</div>

Now this flow is calculating an error-state value, and adding it to `app-db` after every event.
This happens as long as the `:items` have changed... right?
This happens whenever `:items` have changed... right?
Actually, there's another way to make a flow recalculate - we can re-register it.

Let's update the app to display our new error state:
Expand Down
4 changes: 2 additions & 2 deletions docs/data-oriented-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ Look carefully into re-frame, and you'll see the primacy of data everywhere.

There is also string-oriented programming which is what you often employ with regexes or SQL.

We have all used a string containing just the right format with a regex engine. And we've all given a string containing SQL to a database engine, which it knows how to interpret it.
We have all used a string containing just the right format with a regex engine. And we've all given a string containing SQL to a database engine, which knows how to interpret it.

When the strings concerned are literals, this can be straightforward. But it quickly gets ugly if we have to start computing the strings - if we have to use string interpolation to build up the string to be executed. And, of course, this happens a lot with SQL, and it is awful. Consequently, there are a thousand workarounds.

Data is a better medium for computing code, than strings. Datalog (data) is better than SQL (strings).
Data is a better medium for computing code than strings. Datalog (data) is better than SQL (strings).


!!! Note "Multiple Execution Contexts"
Expand Down
8 changes: 4 additions & 4 deletions docs/on-dynamics.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ dynamic process to understand - one that is more cognitively tractable.
But wait, there's more.

We provide data to a pure function as arguments, and they return data, and this data is immutable. This acts to
decoupled a pure functions from "place" - it is insulated from where data is put.
decouple a pure function from any "place" - it is insulated from where data is put.

Because pure functions are decoupled from both "time" and "place",
they can be composed in a maximally mathematical way. This greatly
Expand All @@ -158,8 +158,8 @@ dampens the complexity of runtime dynamics.
>
> -- Joe Armstrong, creator of the Erlang programming language

To understand a function that grabs a banana, you must **also** understand all the runtime dynamics
associated with changes in banana. You must reason globally, not locally. Which is often difficult.
To understand a function that grabs a banana, you must **also** understand the entire dynamics
associated with changes in the jungle. You must reason globally, not locally. Which is often difficult.

## Declarative

Expand Down Expand Up @@ -200,7 +200,7 @@ over the wire from the server.

## Incident report - "Simple Dynamic Process"

While re-fame normally has a simple dynamic process, we acknowledge fat tail risk.
While re-frame normally has a simple dynamic process, we acknowledge fat tail risk.

On May 12th, at approximately 12:47am, re-frame became self-aware. It printed a single console log: "Oh, really? I'll show *you* a simple dynamic process, f#$%ers".

Expand Down

0 comments on commit 36b832e

Please sign in to comment.