mirror of
https://github.com/fluencelabs/aqua-book
synced 2024-12-04 15:20:19 +00:00
GitBook: [master] 29 pages modified
This commit is contained in:
parent
743fe257ba
commit
f6d737e844
@ -11,6 +11,9 @@
|
||||
* [Types](language/types.md)
|
||||
* [Execution flow](language/operators/README.md)
|
||||
* [Sequential](language/operators/sequential.md)
|
||||
* [Conditional](language/operators/conditional.md)
|
||||
* [Parallel](language/operators/parallel.md)
|
||||
* [Iterative](language/operators/iterative.md)
|
||||
* [Expressions](language/expressions/README.md)
|
||||
* [Header](language/expressions/header.md)
|
||||
* [Functions](language/expressions/functions.md)
|
||||
|
@ -1,2 +1,6 @@
|
||||
# Execution flow
|
||||
|
||||
Aqua's main goal is to express how the execution flows: moves from peer to peer, forks to parallel flows and then joins back, uses data from one step in another.
|
||||
|
||||
As the foundation of Aqua is based on π-calculus, finally flow is decomposed into sequential \(seq, .\), conditional \(xor, ^\), parallel \(par, \|\) computations and iterations based on data \(!P\).
|
||||
|
||||
|
126
language/operators/conditional.md
Normal file
126
language/operators/conditional.md
Normal file
@ -0,0 +1,126 @@
|
||||
# Conditional
|
||||
|
||||
Aqua supports branching: you can return one value or another, recover from the error, or check a boolean expression.
|
||||
|
||||
### Contract
|
||||
|
||||
Second branch is executed iff the first branch failed.
|
||||
|
||||
Second branch has no access to the first branch's data.
|
||||
|
||||
Block is considered executed iff any branch was executed successfully.
|
||||
|
||||
Block is considered failed iff the second branch fails to execute.
|
||||
|
||||
### Conditional operations
|
||||
|
||||
#### try
|
||||
|
||||
Tries to perform operations, or swallows the error \(if there's no catch, otherwise after the try block\).
|
||||
|
||||
```text
|
||||
try:
|
||||
-- If foo fails with an error, execution will continue
|
||||
-- You should write your logic in a non-blocking fashion:
|
||||
-- If your code below depends on `x`, it may halt as `x` is not resolved.
|
||||
-- See Conditional return below for workaround
|
||||
x <- foo()
|
||||
```
|
||||
|
||||
#### catch
|
||||
|
||||
Catches the standard error from `try` block.
|
||||
|
||||
```text
|
||||
try:
|
||||
foo()
|
||||
catch e:
|
||||
logError(e)
|
||||
```
|
||||
|
||||
Type of `e` is:
|
||||
|
||||
```text
|
||||
data LastError:
|
||||
instruction: string -- What AIR instruction failed
|
||||
msg: string -- Human-readable error message
|
||||
peer_id: string -- On what peer the error happened
|
||||
```
|
||||
|
||||
#### if
|
||||
|
||||
If corresponds to `match`, `mismatch` extension of π-calculus.
|
||||
|
||||
```text
|
||||
x = true
|
||||
if x:
|
||||
-- always executed
|
||||
foo()
|
||||
|
||||
if x == false:
|
||||
-- never executed
|
||||
bar()
|
||||
|
||||
if x != false:
|
||||
-- executed
|
||||
baz()
|
||||
```
|
||||
|
||||
Currently, you may only use one `==`, `!=` operator in the `if` expression, or compare with true.
|
||||
|
||||
Both operators can be a variable. Variable types must intersect.
|
||||
|
||||
#### else
|
||||
|
||||
Just the second branch of `if`, in case the condition does not hold.
|
||||
|
||||
```text
|
||||
if true:
|
||||
foo()
|
||||
else:
|
||||
bar()
|
||||
```
|
||||
|
||||
If you want to set a variable based on condition, see Conditional return.
|
||||
|
||||
#### otherwise
|
||||
|
||||
You may add `otherwise` to provide recovery for any block or expression:
|
||||
|
||||
```text
|
||||
x <- foo()
|
||||
otherwise:
|
||||
-- if foo can't be executed, then do bar()
|
||||
y <- bar()
|
||||
```
|
||||
|
||||
### Conditional return
|
||||
|
||||
In Aqua, functions may have only one return expression, which is very last. And conditional expressions cannot define the same variable:
|
||||
|
||||
```text
|
||||
try:
|
||||
x <- foo()
|
||||
otherwise:
|
||||
x <- bar() -- Error: name x was already defined in scope, can't compile
|
||||
```
|
||||
|
||||
So to get the value based on condition, we need to use a [writeable collection](../types.md#collection-types).
|
||||
|
||||
```text
|
||||
-- result may have 0 or more values of type string, and is writeable
|
||||
resultBox: *string
|
||||
try:
|
||||
resultBox <- foo()
|
||||
otherwise:
|
||||
resultBox <- bar()
|
||||
|
||||
-- now result contains only one value, let's extract it!
|
||||
result = resultBox!
|
||||
|
||||
-- Type of result is string
|
||||
-- Please note that if there were no writes to resultBox,
|
||||
-- the first use of result will halt.
|
||||
-- So you need to be careful about it and ensure that there's always a value.
|
||||
```
|
||||
|
89
language/operators/iterative.md
Normal file
89
language/operators/iterative.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Iterative
|
||||
|
||||
π-calculus has a notion of repetitive process: `!P = P | !P`. That means, you can always fork a new `P` process if you need it.
|
||||
|
||||
In Aqua, two operations corresponds to it: you can call a service function \(it's just available when it's needed\), and you can use `for` loop to iterate on collections.
|
||||
|
||||
### For expression
|
||||
|
||||
In short, `for` looks like the following:
|
||||
|
||||
```text
|
||||
xs: []string
|
||||
|
||||
for x <- xs:
|
||||
y <- foo(x)
|
||||
|
||||
-- x and y are not accessible there, you can even redefine them
|
||||
x <- bar()
|
||||
y <- baz()
|
||||
```
|
||||
|
||||
### Contract
|
||||
|
||||
Iterations of `for` loop are executed sequentially by default.
|
||||
|
||||
Variables defined inside for loop are not available outside.
|
||||
|
||||
For loop's code has access to all variables above.
|
||||
|
||||
For can be executed on a variable of any [Collection type](../types.md#collection-types).
|
||||
|
||||
### Conditional for
|
||||
|
||||
You can make several trials in a loop, and break once any trial succeeded.
|
||||
|
||||
```text
|
||||
xs: []string
|
||||
|
||||
for x <- xs try:
|
||||
-- Will stop trying once foo succeeds
|
||||
foo(x)
|
||||
```
|
||||
|
||||
Contract is changed as in [Parallel](parallel.md#contract) flow.
|
||||
|
||||
### Parallel for
|
||||
|
||||
Running many operations in parallel is the most commonly used pattern for `for`.
|
||||
|
||||
```text
|
||||
xs: []string
|
||||
|
||||
for x <- xs par:
|
||||
on x:
|
||||
foo()
|
||||
|
||||
-- Once the fastest x succeeds, execution continues
|
||||
-- If you want to make the subsequent execution independent from for,
|
||||
-- mark it with par, e.g.:
|
||||
par continueWithBaz()
|
||||
```
|
||||
|
||||
Contract is changed as in [Conditional](conditional.md#contract) flow.
|
||||
|
||||
### Export data from for
|
||||
|
||||
The way to export data from `for` is the same as in [Conditional return](conditional.md#conditional-return) and [Race patterns](parallel.md#join-behavior).
|
||||
|
||||
```text
|
||||
xs: []string
|
||||
return: *string
|
||||
|
||||
-- can be par, try, or nothing
|
||||
for x <- xs par:
|
||||
on x:
|
||||
return <- foo()
|
||||
|
||||
-- Wait for 6 fastest results -- see Join behavior
|
||||
baz(return!5, return)
|
||||
```
|
||||
|
||||
### For on streams
|
||||
|
||||
For on streams is one of the most complex parts of Aqua.
|
||||
|
||||
Stream forms CRDT.
|
||||
|
||||
Iterations are added when new values are added to a stream.
|
||||
|
96
language/operators/parallel.md
Normal file
96
language/operators/parallel.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Parallel
|
||||
|
||||
Parallel execution is where everything becomes shiny.
|
||||
|
||||
### Contract
|
||||
|
||||
Parallel branches have no access to each other's data. Sync points must be explicit \(see Join behavior\).
|
||||
|
||||
If any branch is executed successfully, the flow execution continues.
|
||||
|
||||
All the data defined in parallel branches is available in the subsequent code.
|
||||
|
||||
### Parallel operations
|
||||
|
||||
As of time of writing, there's only one parallel expression: `par`
|
||||
|
||||
Its syntax is derived from π-calculus notation of parallelism: `A | B`
|
||||
|
||||
```text
|
||||
-- foo and bar will be executed in parallel, if possible
|
||||
foo()
|
||||
par bar()
|
||||
```
|
||||
|
||||
Parallel execution has some implementation limitations:
|
||||
|
||||
* Parallel means independent execution on different peers
|
||||
* No parallelism when executing a script on single peer \(fix planned\)
|
||||
* No concurrency in services: one service instance does only one job in a time. Keep services small \(wasm limitation\)
|
||||
|
||||
### Join behavior
|
||||
|
||||
Join means that data was created by different parallel execution flows and then used on a single peer to perform computations.
|
||||
|
||||
In Aqua, you can refer to previously defined variables. In case of sequential computations, they are available, if execution not failed:
|
||||
|
||||
```text
|
||||
-- Start execution somewhere
|
||||
on peer1:
|
||||
-- Go to peer1, execute foo, remember x
|
||||
x <- foo()
|
||||
|
||||
-- x is available at this point
|
||||
|
||||
on peer2:
|
||||
-- Go to peer2, execute bar, remember y
|
||||
y <- bar()
|
||||
|
||||
-- Both x and y are available at this point
|
||||
-- Use them in a function
|
||||
baz(x, y)
|
||||
```
|
||||
|
||||
Let's make this script parallel: execute `foo` and `bar` on different peers in parallel, then use both to compute `baz`.
|
||||
|
||||
```text
|
||||
-- Start execution somewhere
|
||||
on peer1:
|
||||
-- Go to peer1, execute foo, remember x
|
||||
x <- foo()
|
||||
|
||||
-- Notice par on the next line: it means, go to peer2 in parallel with peer1
|
||||
|
||||
par on peer2:
|
||||
-- Go to peer2, execute bar, remember y
|
||||
y <- bar()
|
||||
|
||||
-- Remember the contract: either x or y is available at this point
|
||||
-- As it's enough to execute just one branch to advance further
|
||||
baz(x, y)
|
||||
```
|
||||
|
||||
What will happen when execution comes to `baz`?
|
||||
|
||||
Actually, the script will be executed twice: first time it will be sent from `peer1`, and second time – from `peer2`. Or another way round: `peer2` then `peer1`, we don't know who is faster.
|
||||
|
||||
When execution will get to `baz` for the first time, [Aqua VM](../../aqua-vm.md) will realize that it lacks some data that is expected to be computed above in the parallel branch. And halt.
|
||||
|
||||
After the second branch executes, VM will be woken up again, reach the same piece of code and realize that now it has enough data to proceed.
|
||||
|
||||
This way you can express race \(see [Collection types](../types.md#collection-types) and [Conditional return](conditional.md#conditional-return) for other uses of this pattern\):
|
||||
|
||||
```text
|
||||
-- Initiate a stream to write into it several times
|
||||
results: *string
|
||||
|
||||
on peer1:
|
||||
results <- foo()
|
||||
|
||||
par on peer2:
|
||||
results <- bar()
|
||||
|
||||
-- When any result is returned, take the first (the fastest) to proceed
|
||||
baz(results!0)
|
||||
```
|
||||
|
@ -1,4 +1,64 @@
|
||||
# Sequential
|
||||
|
||||
Sequential
|
||||
By default, Aqua code is executed line by line, sequentially.
|
||||
|
||||
### Contract
|
||||
|
||||
Data from the first branch is available in the second branch.
|
||||
|
||||
Second branch is executed iff the first branch succeeded.
|
||||
|
||||
If any branch errored, then the whole sequence is errored.
|
||||
|
||||
If all branches executed successfully, then the whole seq is executed successfully.
|
||||
|
||||
### Sequential operations
|
||||
|
||||
#### call arrow
|
||||
|
||||
Any runnable piece of code in Aqua is an arrow from its domain to codomain.
|
||||
|
||||
```text
|
||||
-- Call a function
|
||||
foo()
|
||||
|
||||
-- Call a function that returns smth, assign results to a variable
|
||||
x <- foo()
|
||||
|
||||
-- Call an ability function
|
||||
y <- Peer.identify()
|
||||
|
||||
-- Pass an argument
|
||||
z <- Op.identity(y)
|
||||
```
|
||||
|
||||
When you write `<-`, this means not just "assign results of the function on the right to variable on the left". It means that all the effects are executed: [service](../abilities-and-services.md) may change state, [topology](../topology.md) may be shifted. But you end up in the same topological scope.
|
||||
|
||||
#### on
|
||||
|
||||
`on` denotes the peer where the code must be executed.
|
||||
|
||||
```text
|
||||
func foo():
|
||||
-- Will be executed where `foo` was executed
|
||||
bar()
|
||||
|
||||
-- Move to another peer
|
||||
on another_peer:
|
||||
-- To call bar, we need to leave the peer where we were and get to another_peer
|
||||
-- It's done automagically
|
||||
bar()
|
||||
|
||||
on third_peer via relay:
|
||||
-- This is executed on third_peer
|
||||
-- But we denote that to get to third_peer and to leave third_peer
|
||||
-- an additional hop is needed: get to relay, then to peer
|
||||
bar()
|
||||
|
||||
-- Will be executed in the `foo` call site again
|
||||
-- To get from the previous `bar`, compiler will add a hop to relay
|
||||
bar()
|
||||
```
|
||||
|
||||
See more in [Topology](../topology.md) section.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user