Skip to content

Commit

Permalink
Improve first flow section (#1559)
Browse files Browse the repository at this point in the history
Fixes #614
  • Loading branch information
andrewdavidmackenzie authored Nov 25, 2022
1 parent ea70d58 commit acfcb06
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 190 deletions.
3 changes: 1 addition & 2 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@

# Your First Flow
- [Your First Flow](docs/first_flow/first_flow.md)
- [Understanding it](docs/first_flow/understanding.md)
- [Real Implementation](docs/first_flow/implementation.md)
- [Running the flow](docs/first_flow/implementation.md)
- [Step-by-Step](docs/first_flow/step-by-step.md)
- [Debugging your first flow](docs/first_flow/debugging.md)

Expand Down
62 changes: 56 additions & 6 deletions docs/first_flow/first_flow.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,58 @@
## First flow
Below is a schematic diagram of a 'flow'.
# First flow
Without knowing anything about `flow` and its detailed semantics you might be able to guess what this flow
below does when executed and what the output to STDOUT will be. ![First flow](first.svg)
It is a fibonacci series generator.

Without knowing anything about 'flow' and it's detailed semantics
can you guess what this flow does when executed and what the output to STDOUT will be?
![First flow](first.svg)
## Understanding the flow
NOTE:You can find a complete description of flow semantics in the next
section [Defining Flows](../describing/definition_overview.md)

The next section reveals the answer and walks you thru it.
### Root flow
All flows start with a root "flow definition". Other sub-flows can be nested under the root, via references to
separate flow description files, to enable encapsulation and flow reuse.

In this case it is the only one, and no hierarchy of flows descriptions is used or needed.
You can see the TOML root flow definition for this flow in the flowsample crate's fibonacci sample.
[root.toml](../../flowsamples/fibonacci/root.toml)

### Interaction with the execution environment
The root defines what the interaction with the surrounding execution environment is,
such as [Stdout](../../flowr/src/cli/stdio/stdout.md), or any other `context function` provided by the flow runtime
being used (e.g. `flowr`).

The only interaction with the execution environment in this example is the use of `stdout` to print the numbers
in the series to the Terminal.

### Functions
Functions are stateless, and pure, and just take a set of inputs (one on each of its inputs) and produce an output.

When all the inputs of a function have a value, then the function can run and produce an output, or not
produce outputs, as in the case of the impure `stdout` function.

This flow uses two functions (shown as orange ovals):
- `stdout` from the `context functions` as described above
- `stdout` only has one, unnamed, default input and no outputs. It will print the value on STDOUT of the process
running the flow runner (`flowr`) that is executing the flow.
- the `add` function from the flow standard library `flowstdlib` to add two integers together.
- `add` has two inputs "i1" and "i2" and produces the sum of them on the only, unnamed, "default" output.

### Connections
Connections (the solid lines) take the output of a function when it has ran, and send it to the input of connected
functions. They can optionally have a name.

When a functions has ran, the input values used are made available again at the output.

In this case the following three connections exist:
- "i2" input value is connected back to the "i1" input.
- the output of "add" (the sum of "i1" and "i2") is connected back to the "i2" inputs. This connection has optionally
been called "sum"
- the output of "add" (the sum of "i1" and "i2") is connected to the default input of "Stdout". This connection has
optionally been called "sum"
-
### Initializations
Inputs of processes (flows or functions) can be initialized with a value "Once" (at startup) or "Always" (each time
it ran) using input initializers (dotted lines)

In this example two input initializers are used to setup the series calculation
- "Once" initializer with value "1" in the "i2" input of "add"
- "Once" initializer with value "0" in the "i1" input of "add"
23 changes: 13 additions & 10 deletions docs/first_flow/implementation.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
# Real Implementation
# Running the flow

This flow exists as a sample in the `samples/fibonacci` folder and is written to be as simple as possible,
not using nested flows or similar.
This flow exists as a sample in the `flowsamples/fibonacci` folder. See the
[root.toml](../../flowsamples/fibonacci/root.toml) root flow definition file

### Running the corresponding sample
You can run this first flow and observe its output from the terminal, while in the project root folder:
You can run this flow and observe its output from the terminal, while in the flow project root folder:

```shell script
> cargo run -- flowsamples/fibonacci
> cargo run -p flowc -- -C flowr/src/cli flowsamples/fibonacci
```

`flowc` will compile the flow definition (`root.toml`) and generate the `manifest.json` manifest which is
then run using `flowr`.
`flowc` will compile the flow definition from the root flow definition file (`root.toml`) using the `context functions`
offered by `flowr` (defined in the `flowr/src/cli` folder) to generate a `manifest.json` compiled flow manifest in the
`flowsamples/fibonacci` folder.

`flowc` then runs `flowr` to execute the flow.

`flowr` is a Command Line flow runner and provides implementations for `context` functions to read and write to `stdio` (e.g. `stdout`).

The flow produces a fibonacci series:
The flow will produce a fibonacci series printed to Stdout on the terminal.

```shell script
> cargo run -p flowc -- flowsamples/fibonacci
> cargo run -p flowc -- -C flowr/src/cli flowsamples/fibonacci
Compiling flowstdlib v0.6.0 (/Users/andrew/workspace/flow/flowstdlib)
Finished dev [unoptimized + debuginfo] target(s) in 1.75s
Running `target/debug/flowc flowsamples/first`
Expand Down
216 changes: 82 additions & 134 deletions docs/first_flow/step-by-step.md
Original file line number Diff line number Diff line change
@@ -1,149 +1,97 @@
## Step-by-Step
Here we walk you through the execution of the previous "my first flow" (the fibonacci series sample).

Execution is in terms of Functions. Values are in fact a specific implementation of a
Function, as Values have to store values, unlike functions.
Compiled flows consist of only functions, so flow execution consists of executing functions, or more precisely, jobs
formed from a set of inputs, and a reference to the function implementation.

### Init
The list of Functions is loaded and all are initialized.
This includes making the initial values available (just once) at
the inputs of any values that have initial values specified in the flow description.
The flow manifest (which contains a list of Functions and their output connections) is loaded.

Status (ready to run, pending inputs, blocked etc) of all functions is set based on availability of their inputs and
not being blocked from sending its output.
Any function input that has an input initializer on it, is initialized with the value provided in the initializer.

Any function that has either no inputs (only `context funcitons` are allowed to have no inputs, such as `Stdin`) or
has a value on all of its inputs, is set to the ready state.

### Execution Loop
In general, the execution loop takes the next function that is in the ready state
(has all its input values available, and is not blocked from sending its output by other functions) and runs it.
The next function that is in the ready state (has all its input values available, and is not blocked from sending
its output by other functions) has a job created from its input values and the job is dispatched to be run.

Executors wait for jobs to run, run them and then return the result, that may or may not contain an output value.

That consumed the inputs and sends the output value to all functions connected to the output. That makes that input
available to the other function connected to the output, and it may make that other function ready to run.
Any output value is sent to all functions connected to the output of the function that the job ran for.
Sending an input value to a function may make that function ready to run.

When there are no more functions in the ready to run state, then execution has terminated and the flow ends.
The above is repeated until there are no more functions in the ready state, then execution has terminated and the flow ends.

### Specific Sequence for this example
Below is a description of what happens in the flor runtime to execute the flow.

You can see log output (printed to STDOUT and mixed with the number series output) of what is happening using
the `-v, verbosity <Verbosity Level>` command line option to `flowr`.
- Values accepted (from less to more output verbosity) are: `error` (the default), `warn`, `info` `debug` and `trace`.

#### Init:
* Initial values of 1 are made available in the inputs of "HEAD" and "HEAD-1" values.
* HEAD-1 has input (1) available and is not blocked from sending its outputs, so it is made ready to run.
* HEAD has input (1) available and is not blocked from sending its outputs, so it is made ready to run.
* The "i2" input of the "add" function is initialized with the value 1
* The "ii" input of the "add" function is initialized with the value 0
* The "add" function has a value on all of its inputs, so it is set to the ready state
* STDOUT does not have an input value available so it is not "ready"
* SUM does not have its inputs available so it is not "ready"

#### Loop Starts
ReadyList = HEAD-1(1), HEAD(1)

Next function with status "ready" is run:

- HEAD-1 is run with input 1
- HEAD-1 makes the value 1 available on its output (to STDOUT and SUM)
- HEAD-1 is now blocked from running again until its output to SUM is free
- STDOUT has all inputs available (from "HEAD-1") so is made "ready"
- SUM(1,_) only has one of its inputs available, so it is not made "ready"

ReadyList = HEAD(1), STDOUT(1)

Next function with status "ready" is run:

- "HEAD" is run with input 1
- This updates its value and makes the value 1 available on its outputs (to HEAD-1 and SUM)
- SUM(1,1) now has both inputs available (from HEAD and HEAD-1) so it is made "function"
- HEAD-1(1) has an input value available (from HEAD, but it cannot run as its output is blocked by SUM,
so it is "blocked on output" and not "ready".

ReadyList = STDOUT(1), SUM(1,1)

Next function with status "ready" is run:

- "STDOUT" runs with input 1. It prints "1" on the stdout of the run-time.
> 1
ReadyList = SUM(1,1)

Next function with status "ready" is run:

- "SUM" runs with inputs 1 and 1. It produces the value 2 on its output (to HEAD)
- SUM running consumes its input and unblocks HEAD-1(1) from running
- HEAD has its input available so is made "ready" with input 2

ReadyList = HEAD-1(1), HEAD(2)

Next function with status "ready" is run:

- HEAD-1 is run with input 1. It produces 1 on its output (to STDOUT and SUM)
- STDOUT(1) has its input available so is made "ready"
- SUM(1, _) only has one input available and so is not "ready"

ReadyList = HEAD(2), STDOUT(1)

Next function with status "ready" is run:

- "HEAD" is run with input 2. It produces 2 on its output (to HEAD-1 and SUM)
- SUM(1,2) is made "ready"
- HEAD-1(2) is blocked on sending by SUM

ReadyList = STDOUT(1), SUM(1,2)

Next function with status "ready" is run:

- "STDOUT" runs with input 1. It prints "1" on the stdout of the run-time.
> 1
ReadyList = SUM(1,2)

Next function with status "ready" is run:

- SUM runs with inputs 1 and 2. It produces the value 3 on its output (to HEAD)
- HEAD-1(2) has its output unblocked by SUM and so is made "ready"
- HEAD(3) has its input available so is made "ready"

ReadyList = HEAD-1(2), HEAD(3)

- HEAD-1(2) is run. It produces 2 on its output (to STDOUT and SUM)
- STDOUT(2) has its input avaialble so is made "ready"
- SUM(2, _) lacks an input and is not ready

ReadyList = HEAD(3), STDOUT(2)

- HEAD(3) is run. It produces 3 on its output (to HEAD-1 and SUM)
- SUM(2, 3) is made "ready"
- HEAD-1(3) is blocked on SUM so not "ready"

ReadyList = STDOUT(2), SUM(2,3), HEAD-1(3)

Next function with status "ready" is run:

- STDOUT(2)) runs. It prints "2" on the stdout of the run-time.
> 2
ReadyList = SUM(2,3)

Next function with status "ready" is run:

- SUM(2,3) is run. It produces the value 5 on its output (to HEAD)
- HEAD-1(3) has its output unblocked by SUM and so is made "ready"
- HEAD(5) has its input available so is made "ready"

ReadyList = HEAD-1(3), HEAD(5)

Next function with status "ready" is run:

- HEAD-1(3) is run. It produces 3 on its output (to STDOUT and SUM)
- STDOUT(3) has its input avaialble so is made "ready"
- SUM(3, _) lacks an input and is not ready

ReadyList = HEAD(5), STDOUT(3)

Next function with status "ready" is run:

- HEAD(5) is run. It produces 5 on its output (to HEAD-1 and SUM)
- SUM(3, 5) is made "ready"
- HEAD-1(5) is blocked on SUM so not "ready"

ReadyList = STDOUT(3), SUM(3,5)

Next function with status "ready" is run:

- STDOUT(3)) runs. It prints "3" on the stdout of the run-time.
> 3
and so on, and so forth.... producing a fibonacci series on the standard output of the run-time:
> 1, 1, 2, 3, 5, 8 ...
Ready = ["add"]

- "add" runs with Inputs = (0, 1) and produces output 1
- value 1 from output of "add" is sent to input "i2" of "add"
- "add" only has a value on one input, so is NOT ready
- value 1 from output of "add" is sent to default (only) input of "Stdout"
- "Stdout" has a value on all of its (one) inputs and so is marked "ready"
- input value "i2" (1) of the executed job is sent to input "i1" of "add"
- "add" now has a value on both its inputs and is marked "ready"

Ready = ["Stdout", "add"]

- "Stdout" runs with Inputs = (1) and produces no output
- "Stdout" converts the `number` value to a `String` and prints "1" on the STDOUT of the terminal
- "Stdout" no longer has values on its inputs and is set to not ready

Ready = ["add"]

- "add" runs with Inputs = (1, 1) and produces output 2
- value 2 from output of "add" is sent to input "i2" of "add"
- "add" only has a value on one input, so is NOT ready
- value 2 from output of "add" is sent to default (only) input of "Stdout"
- "Stdout" has a value on all of its (one) inputs and so is marked "ready"
- input value "i2" (1) of the executed job is sent to input "i1" of "add"
- "add" now has a value on both its inputs and is marked "ready"

Ready = ["Stdout", "add"]

- "Stdout" runs with Inputs = (2) and produces no output
- "Stdout" converts the `number` value to a `String` and prints "2" on the STDOUT of the terminal
- "Stdout" no longer has values on its inputs and is set to not ready

Ready = ["add"]

The above sequence proceeds, until eventually:

- `add` function detects a numeric overflow in the add operation and outputs no value.
- No value is fed back to the "i1" input of add
- "add" only has a value on one input, so is NOT ready
- No value is sent to the input of "Stdout"
- "Stdout" no longer has values on its inputs and is set to not ready

Ready = []

No function is ready to run, so flow execution ends.

Resulting in a fibonacci series being output to Stdout
```
1
2
3
5
8
...... lines deleted ......
2880067194370816120
4660046610375530309
7540113804746346429
```
38 changes: 0 additions & 38 deletions docs/first_flow/understanding.md

This file was deleted.

0 comments on commit acfcb06

Please sign in to comment.