From 8edad7bd809d4717982e5901011ec00b06c99122 Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 28 Jun 2021 08:59:09 +0000 Subject: [PATCH 2/6] GitBook: [master] one page modified --- language/types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/language/types.md b/language/types.md index 7265163..a68e89b 100644 --- a/language/types.md +++ b/language/types.md @@ -65,7 +65,7 @@ Every function has an arrow type that maps a list of input types to an optional It can be denoted as: `Type1, Type2 -> Result` -In the type definition, absense of result is denoted with `()`: `string -> ()` +In the type definition, absence of result is denoted with `()`: `string -> ()` The absence of arguments is denoted by nothing: `-> ()` this arrow takes nothing as an argument and has no return type. @@ -83,7 +83,7 @@ x <- arrow() ### Type Alias -For convinience, you can alias a type: +For convenience, you can alias a type: ```python alias MyAlias = ?string From 5fbecee3dfe14d0e53bca37fc1aae4a6b16ba7bf Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 28 Jun 2021 11:27:19 +0000 Subject: [PATCH 3/6] GitBook: [master] 32 pages modified --- SUMMARY.md | 10 +-- language/basics.md | 10 +-- language/flow/README.md | 12 +++ language/flow/conditional.md | 126 +++++++++++++++++++++++++++++ language/flow/iterative.md | 85 ++++++++++++++++++++ language/flow/parallel.md | 148 +++++++++++++++++++++++++++++++++++ language/flow/sequential.md | 64 +++++++++++++++ language/topology.md | 46 +++++++++-- language/types.md | 62 ++++++++++++++- language/variables.md | 47 +++++++---- 10 files changed, 576 insertions(+), 34 deletions(-) create mode 100644 language/flow/README.md create mode 100644 language/flow/conditional.md create mode 100644 language/flow/iterative.md create mode 100644 language/flow/parallel.md create mode 100644 language/flow/sequential.md diff --git a/SUMMARY.md b/SUMMARY.md index 453309d..de72dee 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -11,11 +11,11 @@ * [Types](language/types.md) * [Values](language/variables.md) * [Topology](language/topology.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) + * [Execution flow](language/flow/README.md) + * [Sequential](language/flow/sequential.md) + * [Conditional](language/flow/conditional.md) + * [Parallel](language/flow/parallel.md) + * [Iterative](language/flow/iterative.md) * [Abilities & Services](language/abilities-and-services.md) * [CRDT Streams](language/crdt-streams.md) * [Imports & exports](language/statements-1.md) diff --git a/language/basics.md b/language/basics.md index ff251a2..768470c 100644 --- a/language/basics.md +++ b/language/basics.md @@ -1,17 +1,17 @@ # Basics -Aqua is an opinionated domain-specific language with colons and indentation. +Aqua is an opinionated domain-specific language. It's structured with significant indentation. ```text -- Comments begin with double-dash, ends on the end of line -func foo(): -- Comments are allowed almost everywhere +func foo(): -- Comments can follow the code -- Body of the block expression is indented bar(5) ``` -Values in Aqua have types. Types of values are designated by a colon, like in function arguments definition below. Type of return \(yielded when a function is executed\) is denoted by an arrow pointing to the right. +Values in Aqua have types. Types of values are designated by a colon `:`, like in the function arguments definition below. The type of return \(yielded when a function is executed\) is denoted by an arrow pointing to the right `->`. -Yielding is denoted by an arrow pointing to the left. +Yielding is denoted by an arrow pointing to the left `<-`. ```text -- Define a function that yields a string @@ -36,7 +36,7 @@ Data: Execution: * [Topology](topology.md) – how to express where the code should be executed -* [Execution flow](operators/) – control structures +* [Execution flow](flow/) – control structures Computations: diff --git a/language/flow/README.md b/language/flow/README.md new file mode 100644 index 0000000..9d90b89 --- /dev/null +++ b/language/flow/README.md @@ -0,0 +1,12 @@ +# 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](sequential.md) \(`seq`, `.`\), [conditional](conditional.md) \(`xor`, `+`\), [parallel](parallel.md) \(`par`, `|`\) computations and [iterations](iterative.md) based on data \(`!P`\). For each basic way to organize the flow, Aqua follows a set of rules to execute the operations: + +* What data is available for use? +* What data is exported and can be used below? +* How errors and failures are handled? + +These rules form a contract, as in [design-by-contract](https://en.wikipedia.org/wiki/Design_by_contract) programming. + diff --git a/language/flow/conditional.md b/language/flow/conditional.md new file mode 100644 index 0000000..93af74e --- /dev/null +++ b/language/flow/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 + +The second arm of the conditional operator is executed iff the first arm failed. + +The second arm has no access to the first arm's data. + +A conditional block is considered executed iff any arm was executed successfully. + +A conditional block is considered failed iff the second \(recovery\) arm 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 operands can be variables. + +#### 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/flow/iterative.md b/language/flow/iterative.md new file mode 100644 index 0000000..b0afc21 --- /dev/null +++ b/language/flow/iterative.md @@ -0,0 +1,85 @@ +# 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 and powerful parts of Aqua. See [CRDT streams](../crdt-streams.md) for details. + diff --git a/language/flow/parallel.md b/language/flow/parallel.md new file mode 100644 index 0000000..3d3e515 --- /dev/null +++ b/language/flow/parallel.md @@ -0,0 +1,148 @@ +# Parallel + +Parallel execution is where everything becomes shiny. + +### Contract + +Parallel arms have no access to each other's data. Sync points must be explicit \(see Join behavior\). + +If any arm is executed successfully, the flow execution continues. + +All the data defined in parallel arms is available in the subsequent code. + +### Implementation limitation + +Parallel execution has some implementation limitations: + +* Parallel means independent execution on different peers +* No parallelism when executing a script on a single peer \(fix planned\) +* No concurrency in services: one service instance does only one job simultaneously. Keep services small \(wasm limitation\) + +We might overcome these limitations later, but for now, plan your application design having this in mind. + +### Parallel operations + +### par + +`par` syntax is derived from π-calculus notation of parallelism: `A | B` + +```text +-- foo and bar will be executed in parallel, if possible +foo() +par bar() + +-- It's useful to combine `par` with `on` block, +-- to delegate further execution to different peers. + +-- In this case execution will continue on two peers, independently +on "peer 1": + x <- foo() +par on "peer 2": + y <- bar() + +-- Once any of the previous functions return x or y, +-- execution continues. We don't know the order, so +-- if y is returned first, hello(x) will not execute +hello(x) +hello(y) + +-- You can fix it with par +-- What's comes faster, will advance the execution flow +hello(x) +par hello(y) +``` + +`par` works in infix manner between the previously stated function and the next one. + +#### co + +`co` , short for `coroutine`, prefixes an operation to send it to background. From π-calculus perspective, it's the same as `A | null`, where `null`-process is the one that does nothing and completes instantly. + +```text +-- Let's send foo to background and continue +co foo() + +-- Do something on another peer, not blocking the flow on this one +co on "some peer": + baz() + +-- This foo does not wait for baz() +foo() + +-- Assume that foo is executed on another machine +co try: + x <- foo() +-- bar will not wait for foo to be executed or even launched +bar() +-- bax will wait for foo to complete +-- if foo failed, x will never resolve +-- and bax will never execute +bax(x) +``` + +### Join behavior + +Join means that data was created by different parallel execution flows and then used on a single peer to perform computations. It works the same way for any parallel blocks, be it `par`, `co` or something else \(`for par`\). + +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](../../runtimes/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/flow/sequential.md b/language/flow/sequential.md new file mode 100644 index 0000000..4ed8ded --- /dev/null +++ b/language/flow/sequential.md @@ -0,0 +1,64 @@ +# Sequential + +By default, Aqua code is executed line by line, sequentially. + +### Contract + +Data from the first arm is available in the second branch. + +Second arm is executed iff the first arm succeeded. + +If any arm failed, then the whole sequence is failed. + +If all arms executed successfully, then the whole sequence 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 being \(semantically\) on the same peer where you have called the arrow. + +#### on + +`on` denotes the peer where the code must be executed. `on` is handled sequentially, and the code inside is executed line by line by default. + +```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. + diff --git a/language/topology.md b/language/topology.md index 0553455..7294cfc 100644 --- a/language/topology.md +++ b/language/topology.md @@ -4,9 +4,9 @@ description: Define where the code is to be executed & how to get there # Topology -In Aqua, topology is the major thing. Aqua lets developers to describe the whole distributed workflow in a single script, link data, recover from errors, implement complex patterns like backpressure, and more. +In Aqua, topology is the major thing. Aqua lets developers describe the whole distributed workflow in a single script, link data, recover from errors, implement complex patterns like consensus algorithms, and more. -Topology in Aqua is made declarative way. You need just to say where a piece of code must be executed, on what peer, and optionally – how to get there. Compiler will add all the required network hops. +Topology in Aqua is made declarative way. You need just to say where a piece of code must be executed, on what peer, and optionally – how to get there. The compiler will add all the required network hops. ### On expression @@ -102,6 +102,8 @@ on "peer1" via "relay1": -- On peer1 foo() -- now go -> relay1 -> relay2 -> peer2 + -- going to relay1 to exit peer1 + -- going to relay2 to enable access to peer2 on "peer2" via "relay2": -- On peer2 foo() @@ -112,9 +114,31 @@ on "peer1" via "relay1": foo() ``` +With `on` and `on ... via`, significant indentation changes the place where the code will be executed, and paths that are taken when execution flow "bubbles up" \(see the last call of `foo`\). It's more efficient to keep the flow as flat as it could. Consider the following change of indentation in the previous script, and how it affects execution: + +```haskell +-- From where we are, -> relay1 -> peer1 +on "peer1" via "relay1": + -- On peer1 + foo() +-- now go -> relay1 -> relay2 -> peer2 +-- going to relay1 to exit peer1 +-- going to relay2 to enable access to peer2 +on "peer2" via "relay2": + -- On peer2 + foo() +-- This is executed in the root scope, after we were on peer2 +-- How to get there? +-- Compiler knows the path that just worked +-- So it goes -> relay2 -> (where we were) +foo() +``` + +When the `on` scope is ended, it does not affect any further topology moves. Until you stop indentation, `on` affects the topology and may add additional topology moves, which means more roundtrips and unnecessary latency. + ### Callbacks -What if you want to return something to the initial peer? For example, send the request to a bunch of services, and then render the responses as they come. +What if you want to return something to the initial peer? E.g. implement a request-response pattern. Or send a bunch of requests to different peers, and render responses as they come, in any order. This can be done with callback arguments in the entry function: @@ -149,7 +173,19 @@ func baz(): If you pass a service call as a callback, it will be executed locally on the node where you called it. That might change. -Lambda functions that capture the topologic context of the definition site are planned, not yet there. +Functions that capture the topologic context of the definition site are planned, not yet there. **Proposed** syntax: + +```text +func baz(): + foo = do (x: u32): + -- Executed there, where foo is called + Srv.call(x) + <- x + -- When foo is called, it will get back to this context + bar(foo) +``` + +{% embed url="https://github.com/fluencelabs/aqua/issues/183" caption="Issue for adding \`do\` expression" %} {% hint style="warning" %} Passing service function calls as arguments are very fragile, as it does not track that a service is resolved in the scope of calling. Abilities variance may fix that. @@ -157,7 +193,7 @@ Passing service function calls as arguments are very fragile, as it does not tra ### Parallel execution and topology -When blocks are executed in parallel, it's not always necessary to resolve topology to get to the next peer. Compiler will add topologic hops from the par branch only if data defined in that branch is used down the flow. +When blocks are executed in parallel, it's not always necessary to resolve topology to get to the next peer. The compiler will add topologic hops from the par branch only if data defined in that branch is used down the flow. {% hint style="danger" %} What if all branches do not return? Execution will halt. Be careful, use `co` if you don't care about the returned data. diff --git a/language/types.md b/language/types.md index a68e89b..7742a42 100644 --- a/language/types.md +++ b/language/types.md @@ -39,7 +39,7 @@ Immutable collection with 0 or 1 value: `?` Appendable collection with 0..N values: `*` -Any data type can be prepended with a quantifier: `*u32`, `[][]string`, `?ProductType` – these types are absolutely correct. +Any data type can be prefixed with a quantifier: `*u32`, `[][]string`, `?ProductType` – these types are absolutely correct. You can access a distinct value of a collection with `!` operator, optionally followed by an index. @@ -93,13 +93,69 @@ alias MyAlias = ?string Aqua is made for composing data on the open network. That means that you want to compose things if they do compose, even if you don't control its source code. -Therefore Aqua follows the structural typing paradigm: if a type contains all the expected data, than it fits. For example, you can pass `u8` in place of `u16` or `i16`. Or `?bool` in place of `[]bool`. Or `*string` instead of `?string` or `[]string`. The same holds for products. +Therefore Aqua follows the structural typing paradigm: if a type contains all the expected data then it fits. For example, you can pass `u8` in place of `u16` or `i16`. Or `?bool` in place of `[]bool`. Or `*string` instead of `?string` or `[]string`. For arrow types, Aqua checks variance on arguments, contravariance on the return type. +```text +-- We expect u32 +xs: *u32 + +-- u16 is less then u32 +foo1: -> u16 +-- works +xs <- foo1() + +-- i32 has sign, so cannot fit into u32 +foo2: -> i32 +-- will fail +xs <- foo2() + +-- Function takes an arrow as an argument +func bar(callback: u32 -> u32): ... + + +foo3: u16 -> u16 + +-- Will not work +bar(foo3) + +foo4: u16 -> u64 + +-- Works +bar(foo4) +``` + +Arrow type `A: D -> C` is a subtype of `A1: D1 -> C1`, if `D1` is a subtype of `D` and `C` is a subtype of `C1`. + ### Type of a Service and a file -A service type is a product of arrows. File is a product of all defined constants and functions \(treated as arrows\). Type definitions in the file does not go to the file type. +A service type is a product of arrows. + +```text +service MyService: + foo(arg: string) -> bool + +-- type of this service is: +data MyServiceType: + foo: string -> bool +``` + +The file is a product of all defined constants and functions \(treated as arrows\). Type definitions in the file do not go to the file type. + +```text +-- MyFile.aqua + +func foo(arg: string) -> bool: + ... + +const flag ?= true + +-- type of MyFile.aqua +data MyServiceType: + foo: string -> bool + flag: bool +``` {% embed url="https://github.com/fluencelabs/aqua/blob/main/types/src/main/scala/aqua/types/Type.scala" caption="See the types system implementation" %} diff --git a/language/variables.md b/language/variables.md index 977779e..d50de1a 100644 --- a/language/variables.md +++ b/language/variables.md @@ -1,8 +1,22 @@ # Values -Aqua is all about combining data and computations. Underlying runtime \([AquaVM](https://github.com/fluencelabs/aquavm)\) tracks what data comes from what origin, which constitutes the foundation for distributed systems security. That approach, driven by π-calculus and security considerations of open-by-default networks and distributed applications as custom application protocols, also puts constraints on the language that configures it. +Aqua is all about the composition of data and computations. Underlying runtime \([AquaVM](https://github.com/fluencelabs/aquavm)\) tracks what data comes from what origin, which constitutes the foundation for distributed systems security. That approach, driven by π-calculus and security considerations of open-by-default networks and distributed applications as custom application protocols, puts constraints on the Aqua language. -Namely, values form VDS \(Verifiable Data Structures\). All operations on values must retain security invariants. Hence values are immutable, except [writeable collections](types.md#collection-types) \(streams\). +Values in Aqua are backed by VDS \(Verifiable Data Structures\) in the runtime. All operations on values must keep the authenticity of data, prooved by signatures under the hood. + +That's why values are immutable. Changing the value effectively makes a new one: + +```text +x = "hello" +y = "world" + +-- despite the sources of x and y, z's origin is "peer 1" +-- and we can trust value of z as much as we trust "peer 1" +on "peer 1": + z <- concat(x, y) +``` + +More on that in the Security section. Now let's see how we can work with values inside the language. ### Arguments @@ -19,7 +33,7 @@ func foo(arg: i32, log: string -> ()): ### Return values -That's the second way to get data with a name. +You can assign results of an arrow call to a name, and use this returned value in the code below. ```text -- Imagine a Stringify service that's always available @@ -43,10 +57,11 @@ func foo(arg: i32, log: *string): ### Literals -Aqua supports just a few literals: numbers, quoted strings, booleans. You [cannot make a structure](https://github.com/fluencelabs/aqua/issues/167) in Aqua. +Aqua supports just a few literals: numbers, quoted strings, booleans. You [cannot init a structure](https://github.com/fluencelabs/aqua/issues/167) in Aqua, only obtain it as a result of a function call. ```text -- String literals cannot contain double quotes +-- No single-quoted strings allowed, no escape chars. foo("double quoted string literal") -- Booleans are true and false @@ -66,7 +81,7 @@ bar(-0.2) ### Getters -In Aqua, you can use a getter to peak into a field of a product, or indexed element in an array. +In Aqua, you can use a getter to peak into a field of a product or indexed element in an array. ```text data Sub: @@ -90,14 +105,14 @@ func foo(e: Example): Note that `!` operator may fail or halt: -* If it is called on an immutable collection, it will fail if collection is shorter and has no given index; you can handle it with [try](operators/conditional.md#try) or [otherwise](operators/conditional.md#otherwise). -* If it is called on an appendable stream, it will wait for some parallel append operation to fulfill, see [Join behavior](operators/parallel.md#join-behavior). +* If it is called on an immutable collection, it will fail if the collection is shorter and has no given index; you can handle it with [try](flow/conditional.md#try) or [otherwise](flow/conditional.md#otherwise). +* If it is called on an appendible stream, it will wait for some parallel append operation to fulfill, see [Join behavior](flow/parallel.md#join-behavior). `!` operator cannot be used with non-literal indices: you can use `!2`, but not `!x`. It's planned to be fixed. ### Assignments -Assignments do nothing new, just gives a name to a value with applied getter, or name a literal. +Assignments do nothing new, just give a name to a value with applied getter, or name a literal. ```text func foo(arg: bool, e: Example): @@ -105,15 +120,15 @@ func foo(arg: bool, e: Example): a = arg -- Assign the name b to value of e.child b = e.child - -- Create a literal + -- Create a named literal c = "just string value" ``` ### Constants -Constants are like assignments, but in the root scope. Can be used in all function bodies, textually below the place of const definition. +Constants are like assignments but in the root scope. Can be used in all function bodies, textually below the place of const definition. -You can change the compilation results with overriding a constant. Override should be of the same type \(or a subtype\). +You can change the compilation results by overriding a constant. Override should be of the same type \(or a subtype\). Constant values must resolve to a literal. @@ -133,7 +148,7 @@ func bar(): ### Visibility scopes -Visibility scopes follows the contracts of execution flow. +Visibility scopes follow the contracts of execution flow. By default, everything defined textually above is available below. With some exceptions. @@ -149,7 +164,7 @@ func bar(): foo() -- a inside fo is 5 ``` -[For loop](operators/iterative.md#export-data-from-for) does not export anything from it: +[For loop](flow/iterative.md#export-data-from-for) does not export anything from it: ```text func foo(): @@ -162,7 +177,7 @@ func foo(): z = 7 ``` -[Parallel](operators/parallel.md#join-behavior) branches have [no access](https://github.com/fluencelabs/aqua/issues/90) to each other's data: +[Parallel](flow/parallel.md#join-behavior) branches have [no access](https://github.com/fluencelabs/aqua/issues/90) to each other's data: ```text -- This will deadlock, as foo branch of execution will @@ -174,7 +189,7 @@ par y <- bar(x) baz(x, y) ``` -Recovery branches in [conditional flow](operators/conditional.md) has no access to the main branch. Main branch exports values, recovery branch does not: +Recovery branches in [conditional flow](flow/conditional.md) has no access to the main branch. Main branch exports values, recovery branch does not: ```text try: @@ -223,5 +238,5 @@ nilString: *string fn(nilString) ``` -One of the most frequently used patterns for streams is [Conditional return](operators/conditional.md#conditional-return). +One of the most frequently used patterns for streams is [Conditional return](flow/conditional.md#conditional-return). From 87fcb147117cedafe60caf6618a155f5c7c1e05f Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 28 Jun 2021 11:54:49 +0000 Subject: [PATCH 4/6] GitBook: [master] one page modified --- language/abilities-and-services.md | 65 ++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/language/abilities-and-services.md b/language/abilities-and-services.md index 8c3d1fc..ad1a6b8 100644 --- a/language/abilities-and-services.md +++ b/language/abilities-and-services.md @@ -1,10 +1,67 @@ # Abilities & Services -Ability as a concept of "what is possible in this context". +While Execution flow organizes the flow from peer to peer, Abilities & Services describe what exactly can be called on these peers, and how to call it. -A Service as the ability to call certain functions on a host. Service resolution: set service ID in scope. +Ability is a concept of "what is possible in this context": like a peer-specific trait or a typeclass. It will be better explained once abilities passing is implemented. -Imported file as an ability. +{% embed url="https://github.com/fluencelabs/aqua/issues/33" %} -Ability variance in functions: how "import abilities" works. +### Services + +A Service interfaces functions \(often WASM ones\) executable on a peer. Example of service definition: + +```text +service MyService: + foo(arg: string) -> string + bar() -> bool + baz(arg: i32) +``` + +Service functions in Aqua have no function body. Computations, of any complexity, are implemented with any programming language that fits, and then brought to the Aqua execution context. Aqua calls these functions but does not peak into what's going on inside. + +#### Built-in services + +Some services may be singletons available on all peers. Such services are called built-ins, and are always available in any scope. + +```text +-- Built-in service has a constant ID, so it's always resolved +service Op("op"): + noop() + +func foo(): + -- Call the noop function of "op" service locally + Op.noop() +``` + +#### Service resolution + +A peer may host many services of the same type. To distinguish services from each other, Aqua requires Service resolution to be done: that means, the developer must provide an ID of the service to be used on the peer. + +```text +service MyService: + noop() + +func foo(): + -- Will fail + MyService.noop() + + -- Resolve MyService: it has id "noop" + MyService "noop" + + -- Can use it now + MyService.noop() + + on "other peer": + -- Should fail: we haven't resolved MyService ID on other peer + MyService.noop() + + -- Resolve MyService on peer "other peer" + MyService "other noop" + MyService.noop() + + -- Moved back to initial peer, here MyService is resolved to "noop" + MyService.noop() +``` + +There's no way to call an external function in Aqua without defining all the data types and the service type. One of the most convinient ways to do it is to generate Aqua types from WASM code in Marine \[link to Marine docs\]. From 41915e660131d39e76159c0005932e63cb7c51be Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 28 Jun 2021 12:22:13 +0000 Subject: [PATCH 5/6] GitBook: [master] one page modified --- language/statements-1.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/language/statements-1.md b/language/statements-1.md index 4cc81a5..bf23012 100644 --- a/language/statements-1.md +++ b/language/statements-1.md @@ -1,10 +1,27 @@ # Imports & exports -How imports are organized and works internally +Aqua source file has head and body. The body contains function definitions, services, types, constants. Header manages what is imported from other files, and what is exported from this one. -How to export functions and types from a file +### Import expression + +The main way to import a file is via `import` expression: + +```text +import "@fluencelabs/aqua-lib/builtin.aqua" + +func foo(): + Op.noop() +``` + +Aqua compiler takes a source directory and a list of import directories \(usually with `node_modules` as a default\). You can use relative paths to `.aqua` files, relatively to the current file's path, and to import folders. + +Everything defined in the file is imported into the current namespace. + +### `Use` expression + +Use expression makes it possible to import a subset of a file, or to alias the imports to avoid collisions. + +{% embed url="https://github.com/fluencelabs/aqua/issues/30" %} -Relationships between exports and abilities -Advanced topic: definition of the imports & exports problem & how Aqua Linker solves it. From efaeb440aa50b8e53c31ac3f749114733abf5b75 Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 28 Jun 2021 12:55:13 +0000 Subject: [PATCH 6/6] GitBook: [master] 31 pages modified --- language/crdt-streams.md | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/language/crdt-streams.md b/language/crdt-streams.md index f4ebdfb..2e41f88 100644 --- a/language/crdt-streams.md +++ b/language/crdt-streams.md @@ -1,2 +1,85 @@ # CRDT Streams +In Aqua, ordinary value is a name that points to a single result: + +```text +value <- foo() +``` + +Stream is a name that points to a number of results \(zero or more\): + +```text +value: *string +value <- foo() +value <- foo() +``` + +Stream is a kind of [collection](types.md#collection-types), and can be used where other collections are: + +```text +func foo(peer: string, relay: ?string): + on peer via relay: + Op.noop() + +-- Dirty hack for lack of type variance, and lack of cofunctors +service OpStr("op"): + identity: string -> string + +func bar(peer: string, relay: string): + relayMaybe: *string + if peer != %init_peer_id%: + -- To write into a stream, function call is required + relayMaybe <- OpStr.identity(relay) + -- Pass a stream as an optional value + foo(peer, relayMaybe) +``` + +But the most powerful uses of streams come along with parallelism, which incurs non-determinism. + +### Streams lifecycle and guarantees + +Streams lifecycle can be divided into three stages: + +* Source: \(Parallel\) Writes to a stream +* Map: Handling the stream values +* Sink: Converting the resulting stream into scalar + +Consider the following example: + +```text +func foo(peers: []string) -> string: + resp: *string + + -- Will go to all peers in parallel + for p <- peers par: + on p: + -- Do something + resp <- Srv.call() + + resp2: *string + + -- What is resp at this point? + for r <- resp par: + on r: + resp2 <- Srv.call() + + -- Wait for 6 responses + Op.identity(resp2!5) + -- Once we have 5 responses, merge them + r <- Srv.concat(resp2) + <- r + +``` + +In this case, for each peer in peers, something is going to be written into resp stream. + +Every peer p in peers does not know anything about how the other iterations proceed. + +Once something is written to resp stream, the second for is triggered. It's the mapping stage. + +And then the results are sent to the first peer, to call Op.identity there. This Op.identity waits until element number 5 is defined on resp2 stream. + +When it is, stream as a whole is consumed to produce a scalar value, which is returned. + +During execution, involved peers have different views on the state of execution: parallel branches of for have no access to each other's data. Finally, execution flows to the initial peer. Initial peer merges writes to the resp stream, and merges writes to the resp2 stream. It's done in conflict-free fashion. More than that, head of resp, resp2 streams will not change from each peer's point of view: it's immutable, and new values are only appended. However, different peers may have different order of the stream values, depending on the order of receiving these values. +