diff --git a/SUMMARY.md b/SUMMARY.md index 59a468b..baf3a15 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -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) diff --git a/language/operators/README.md b/language/operators/README.md index aa0a7f5..d4196d8 100644 --- a/language/operators/README.md +++ b/language/operators/README.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\). + diff --git a/language/operators/conditional.md b/language/operators/conditional.md new file mode 100644 index 0000000..85579b7 --- /dev/null +++ b/language/operators/conditional.md @@ -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. +``` + diff --git a/language/operators/iterative.md b/language/operators/iterative.md new file mode 100644 index 0000000..3766ce9 --- /dev/null +++ b/language/operators/iterative.md @@ -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. + diff --git a/language/operators/parallel.md b/language/operators/parallel.md new file mode 100644 index 0000000..087982d --- /dev/null +++ b/language/operators/parallel.md @@ -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) +``` + diff --git a/language/operators/sequential.md b/language/operators/sequential.md index 3d05c87..141a73e 100644 --- a/language/operators/sequential.md +++ b/language/operators/sequential.md @@ -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.