Merge pull request #5 from fluencelabs/alpha

Alpha
This commit is contained in:
boneyard93501 2021-07-01 11:55:45 -05:00 committed by GitHub
commit 9efda4760e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 300 additions and 189 deletions

View File

@ -1,8 +1,23 @@
# Aqua
# Introduction
Fluence is an open protocol and a framework for internet or private cloud applications. Fluence provides a peer-to-peer development stack so that you can create applications free of proprietary cloud platforms, centralized APIs, and untrustworthy third-parties. The Fluence stack is open source and is maintained and governed by a community of developers.
At the core of Fluence is the open-source language **Aqua** that allows for the programming of peer-to-peer scenarios separately from the computations on peers. Applications are turned into hostless workflows over distributed function calls, which enables various levels of decentralization: from handling by a limited set of servers to complete peer-to-peer architecture by connecting user devices directly.
{% embed url="https://youtu.be/M\_u-EnWrMOQ" %}
This book is dedicated to all things Aqua and currently in its **alpha** version and we expect to expand both Aqua's breadth and depth coverage over the coming weeks.
Stay in touch or contact us via the following channels:
* [Discord](https://discord.gg/)
* [Telegram](https://t.me/fluence_project)
* [Aqua Github](https://github.com/fluencelabs/aqua)
* [Youtube](https://www.youtube.com/channel/UC3b5eFyKRFlEMwSJ1BTjpbw)
[Aqua](https://github.com/fluencelabs/aqua) is a purpose-designed language to compose distributed services hosted on peer-to-peer nodes into applications and backends and part of Fluence Labs' Aquamarine stack and peer-to-peer programming model.
In addition to the language specification, Aqua provides a compiler, intermediate representation \(AIR\) and an execution stack, Aqua VM.

View File

@ -1,11 +1,9 @@
# Table of contents
* [Aqua](README.md)
* [Tour De Aqua](tour-de-aqua.md)
* [Introduction](README.md)
* [Getting Started](getting-started/README.md)
* [Installation](getting-started/installation.md)
* [Debugging And Testing](getting-started/debugging-and-testing.md)
* [Tools](getting-started/tools.md)
* [Quick Start](getting-started/quick-start.md)
* [Language](language/README.md)
* [Basics](language/basics.md)
* [Types](language/types.md)
@ -18,17 +16,11 @@
* [Iterative](language/flow/iterative.md)
* [Abilities & Services](language/abilities-and-services.md)
* [CRDT Streams](language/crdt-streams.md)
* [Imports & exports](language/statements-1.md)
* [Imports And Exports](language/statements-1.md)
* [Expressions](language/expressions/README.md)
* [Header](language/expressions/header.md)
* [Functions](language/expressions/functions.md)
* [Services](language/expressions/services.md)
* [Type definitions](language/expressions/type-definitions.md)
* [Overrideable constants](language/expressions/overrideable-constants.md)
* [Runtimes](runtimes/README.md)
* [Aqua VM](runtimes/aqua-vm.md)
* [Aqua Patterns](aqua-patterns.md)
* [Library \(BuiltIns\)](library-builtins.md)
* [Appendix](appendix.md)
* [Foundations: π-calculus](foundations-p-calculus.md)

View File

@ -1,8 +1,6 @@
# Getting Started
## Installation
[Aqua](https://github.com/fluencelabs/aqua), part of Fluence Lab's Aquamarine Web3 stack, is a purpose-built language to program peer-to-peer networks and compose distributed services hosted on peer-to-peer nodes into applications and backends.
* native
* container
* VSCode extension\(s\)
In addition to the language specification, Aqua provides a compiler, which produces Aqua Intermediary Representation \(AIR\) and an execution stack, Aqua VM, that is part of every Fluence node implementation to execute AIR.

View File

@ -1,5 +1,30 @@
# Installation
* native tools
* devcontianer ?
Both the Aqua compiler and support library can be installed natively with `npm`
To install the compiler:
```bash
npm -g install @fluencelabs/aqua-cli
```
and to make the Aqua library available to Typescript applications:
```bash
npm -g install @fluencelabs/aqua-lib
```
Moreover, a VSCode syntax-highlighting extension is available. In VSCode, click on the Extensions button, search for `aqua`and install the extension.
![aqua extension for VSCode](../.gitbook/assets/screen-shot-2021-06-29-at-1.06.39-pm.png)

View File

@ -0,0 +1,101 @@
# Quick Start
Every Fluence reference node comes with a set of builtin services that are accessible to Aqua programs. Let's use those readily available services to get the timestamp of a few of our peer-to-peer neighbourhood nodes with Aqua.
{% tabs %}
{% tab title="Timestamps With Aqua" %}
```text
-- timestamp_getter.aqua
-- bring the builtin services into scope
import "builtin.aqua"
-- create and identity service to join our results
service Op2("op"):
identity(s: u64)
array(a: string, b: u64) -> string
-- function to get ten timestamps from our Kademlia
-- neighborhood peers and return as an array of u64 timestamps
-- the function arguement node is our peer id
func ts_getter(node: string) -> []u64:
-- create a streaming variable
res: *[]u64
-- execute on the pecified peer
on node:
-- get the base58 representation of the peer id
k <- Op.string_to_b58(node)
-- find all (default 20) neighborhood peers from k
nodes <- Kademlia.neighborhood(k, false)
-- for each peer in our neighborhood and in parallel
for n <- nodes par:
on n:
-- try and get the peer's timestamp
try:
res <- Peer.timestamp_ms()
-- flatten nine of our joined results
Op2.identity(res!9)
-- return an array of ten timestamps
<- res
```
{% endtab %}
{% endtabs %}
The Aqua script essentially creates a workflow originating from our client peer to enumerate neighbor peers for our reference node, calls on the builtin timestamp service on each peer in parallel, joins the results stream after we collect ten timestamps and return our u64 array of timestamps back to the client peer.
See the [ts-oracle example](https://github.com/fluencelabs/examples/tree/main/ts-oracle) for the corresponding Aqua files in the `aqua-script` directory. Now that we have our script, let's compile it with the `aqua-cli` tool and find our AIR file in the `air-scripts` directory:
{% tabs %}
{% tab title="Compile" %}
```bash
aqua-cli -i aqua-scripts -o air-scripts -a
```
{% endtab %}
{% tab title="Result" %}
```bash
# in the air-script dir you should have the following file
timestamp_getter.ts_getter.air
```
{% endtab %}
{% endtabs %}
Once we have our AIR file we can either use a Typescript or command line client. Let's use the command line client `flidst`see third tab for installation instructions, if needed:
{% tabs %}
{% tab title="Run Air scripts" %}
```text
# execute the AIR script from our compile phase with a peer id
fldist run_air -p air-scripts/timestamp_getter.ts_getter.air -d '{"node":"12D3KooWHLxVhUQyAuZe6AHMB29P7wkvTNMn7eDMcsqimJYLKREf"}' --generated
```
{% endtab %}
{% tab title="Result" %}
```
# here we go: ten timestamps in micro seconds obtained in parallel
[
[
1624928596292,
1624928596291,
1624928596291,
1624928596299,
1624928596295,
1624928596286,
1624928596295,
1624928596284,
1624928596293,
1624928596289
]
]
```
{% endtab %}
{% tab title="Installing fldist" %}
```
# if you don't have fldist on your machine:
npm -g install @fluencelabs/fldist
```
{% endtab %}
{% endtabs %}
And that's it. We now have ten timestamps right from our selected peer's neighbors.

View File

@ -1,6 +1,6 @@
# Abilities & Services
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.
While [Execution flow](flow/) organizes the flow from peer to peer, Abilities & Services describe what exactly can be called on these peers, and how to call it.
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.
@ -8,9 +8,9 @@ Ability is a concept of "what is possible in this context": like a peer-specific
## Services
A Service interfaces functions \(often WASM ones\) executable on a peer. Example of service definition:
A Service interfaces functions \(often provided via WebAssembly interface\) executable on a peer. Example of service definition:
```text
```haskell
service MyService:
foo(arg: string) -> string
bar() -> bool
@ -19,11 +19,11 @@ service MyService:
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
#### 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
```haskell
-- Built-in service has a constant ID, so it's always resolved
service Op("op"):
noop()
@ -33,11 +33,12 @@ func foo():
Op.noop()
```
### Service resolution
#### 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
```haskell
service MyService:
noop()
@ -63,5 +64,6 @@ func foo():
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\].
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 convenient ways to do it is to generate Aqua types from Wasm code in Marine.

View File

@ -2,16 +2,16 @@
Aqua is an opinionated domain-specific language. It's structured with significant indentation.
```text
```haskell
-- Comments begin with double-dash and end with the line (inline)
func foo(): -- Comments are allowed almost everywhere
-- Body of the block expression is indented
bar(5)
```
Values in Aqua have types, which are designated by a colon, `:`, as seen in function signature below. The type of a return, which is yielded when a function is executed, is denoted by an arrow pointing to the right `->` , whereas yielding is denoted by an arrow pointing to the left `<-`.
Values in Aqua have types, which are designated by a colon, `:`, as seen in the function signature below. The type of a return, which is yielded when a function is executed, is denoted by an arrow pointing to the right `->` , whereas yielding is denoted by an arrow pointing to the left `<-`.
```text
```haskell
-- Define a function that yields a string
func bar(arg: i16) -> string:
-- Call a function

View File

@ -1,22 +1,22 @@
# CRDT Streams
In Aqua, ordinary value is a name that points to a single result:
In Aqua, an ordinary value is a name that points to a single result:
```text
```haskell
value <- foo()
```
Stream is a name that points to a number of results \(zero or more\):
A stream , on the other hand, is a name that points to zero or more results:
```text
```haskell
value: *string
value <- foo()
value <- foo()
```
Stream is a kind of [collection](types.md#collection-types), and can be used where other collections are:
Stream is a kind of [collection](types.md#collection-types) and can be used in place of other collections:
```text
```haskell
func foo(peer: string, relay: ?string):
on peer via relay:
Op.noop()
@ -34,19 +34,19 @@ func bar(peer: string, relay: string):
foo(peer, relayMaybe)
```
But the most powerful uses of streams come along with parallelism, which incurs non-determinism.
But the most powerful use of streams pertains to their use with parallel execution, which incurs non-determinism.
## Streams lifecycle and guarantees
### Streams: Lifecycle And Guarantees
Streams lifecycle can be divided into three stages:
A stream's lifecycle can be separated into three stages:
* Source: \(Parallel\) Writes to a stream
* Map: Handling the stream values
* Sink: Converting the resulting stream into scalar
* Map: Handles the stream values
* Sink: Converts the resulting stream into a scalar
Consider the following example:
```text
```haskell
func foo(peers: []string) -> string:
resp: *string
@ -70,15 +70,14 @@ func foo(peers: []string) -> string:
<- r
```
In this case, for each peer in peers, something is going to be written into resp stream.
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.
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.
Once something is written to `resp` stream, the second for is triggered. This is 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.
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.
When the join is complete, the stream is consumed by the concatenation service to produce a scalar value, which is returned.
During execution, involved peers have different views on the state of execution: each of the `for` parallel branches have no view or access to the other branches' data and eventually, the execution flows to the initial peer. The initial peer then merges writes to the `resp` stream and to the `resp2` stream, respectively. These writes are done in conflict-free fashion. Furthermore, the respective heads of the `resp`, `resp2` streams will not change from each peer's point of view as they are immutable and new values can only be appended. However, different peers may have a different order of the stream values depending on the order of receiving these values.

View File

@ -10,15 +10,21 @@ Essentially, a function is an arrow. The function call is an expression that con
Finally, all a function does is call its arguments or service functions.
```text
```haskell
service MySrv:
foo()
func do_something(): -- arrow of type: -> ()
MySrv "srv id"
MySrv.foo()
MySrv.foo()
```
{% hint style="warning" %}
TODO
* list all expressions
* for each, explain the contract and provide a use case
{% endhint %}

View File

@ -1,16 +1,16 @@
# Header
### Header expressions
## Header expressions
`import`
### `import`
The `import` expression brings everything defined within the imported file into the scope.
```text
```haskell
import "path/to/file.aqua"
```
The to be imported file path is first resolved relative to the source file path followed by checking for an `-imports` directories.
The to be imported file path is first resolved relative to the source file path followed by checking for an `-imports` directories.
See [Imports & Exports](../statements-1.md) for details.

View File

@ -4,15 +4,13 @@ description: Static configuration pieces that affect compilation
# Overrideable constants
`const`
### `const`
Constant definition.
Constants can be used all across functions, exported and imported. If a constant is defined using `?=` , it can be overriden by value via compiler flags or imported values.
```text
```haskell
-- This can be overriten with -const "target_peer_id = \"other peer id\""
const target_peer_id ?= "this is a target peer id"

View File

@ -1,6 +1,6 @@
# Services
`service`
### `service`
Service definition.
@ -10,11 +10,11 @@ In the service definition, you enumerate all the functions, their names, argumen
Services that are a part of the protocol, i.e. are available from the peer node, come along with IDs. Example of predefined service:
```text
```haskell
service Peer("peer"):
foo() -- no arguments, no return
bar(i: bool) -> bool
func usePeer() -> bool:
Peer.foo() -- results in a call of service "peer", function "foo", on current peer ID
z <- Peer.bar(true)
@ -23,11 +23,11 @@ func usePeer() -> bool:
Example of a custom service:
```text
```haskell
service MyService:
foo()
bar(i: bool, z: i32) -> string
func useMyService(k: i32) -> string:
-- Need to tell the compiler what does "my service" mean in this scope
MyService "my service id"
@ -36,7 +36,7 @@ func useMyService(k: i32) -> string:
-- Need to redefine MyService in scope of this peer as well
MyService "another service id"
z <- MyService.bar(false, k)
<- z
<- z
```
Service definitions have types. Type of a service is a product type of arrows. See [Types](../types.md#type-of-a-service-and-a-file).

View File

@ -1,23 +1,23 @@
# Type definitions
`data`
### `data`
[Product type](../types.md#products) definition. See [Types](../types.md) for details.
```text
```haskell
data SomeType:
fieldName: FieldType
otherName: OtherType
third: []u32
```
`alias`
### `alias`
Aliasing a type to a name.
It may help with self-documented code and refactoring.
```text
```haskell
alias PeerId: string
alias MyDomain: DomainType
```

View File

@ -4,13 +4,10 @@ Aqua supports branching: you can return one value or another, recover from the e
## 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.
* The second arm of the conditional operator is executed if and only if the first arm failed.
* The second arm has no access to the first arm's data.
* A conditional block is considered "executed" if and only if any arm was executed successfully.
* A conditional block is considered "failed" if and only if the second \(recovery\) arm fails to execute.
## Conditional operations
@ -18,7 +15,7 @@ A conditional block is considered failed iff the second \(recovery\) arm fails t
Tries to perform operations, or swallows the error \(if there's no catch, otherwise after the try block\).
```text
```haskell
try:
-- If foo fails with an error, execution will continue
-- You should write your logic in a non-blocking fashion:
@ -31,7 +28,7 @@ try:
Catches the standard error from `try` block.
```text
```haskell
try:
foo()
catch e:
@ -40,7 +37,7 @@ catch e:
Type of `e` is:
```text
```haskell
data LastError:
instruction: string -- What AIR instruction failed
msg: string -- Human-readable error message
@ -51,7 +48,7 @@ data LastError:
If corresponds to `match`, `mismatch` extension of π-calculus.
```text
```haskell
x = true
if x:
-- always executed
@ -74,7 +71,7 @@ Both operands can be variables.
Just the second branch of `if`, in case the condition does not hold.
```text
```haskell
if true:
foo()
else:
@ -87,7 +84,7 @@ If you want to set a variable based on condition, see Conditional return.
You may add `otherwise` to provide recovery for any block or expression:
```text
```haskell
x <- foo()
otherwise:
-- if foo can't be executed, then do bar()
@ -98,7 +95,7 @@ otherwise:
In Aqua, functions may have only one return expression, which is very last. And conditional expressions cannot define the same variable:
```text
```haskell
try:
x <- foo()
otherwise:
@ -107,7 +104,7 @@ otherwise:
So to get the value based on condition, we need to use a [writeable collection](../types.md#collection-types).
```text
```haskell
-- result may have 0 or more values of type string, and is writeable
resultBox: *string
try:

View File

@ -1,14 +1,15 @@
# 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.
π-calculus has a notion of the 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.
In Aqua, two operations correspond 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
## For expression
In short, `for` looks like the following:
```text
```haskell
xs: []string
for x <- xs:
@ -21,19 +22,16 @@ 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.
* 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`
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
```haskell
xs: []string
for x <- xs try:
@ -41,9 +39,9 @@ for x <- xs try:
foo(x)
```
Contract is changed as in [Parallel](parallel.md#contract) flow.
The contract is changed as in [Parallel](parallel.md#contract) flow.
## Parallel for
### Parallel `for`
Running many operations in parallel is the most commonly used pattern for `for`.
@ -60,13 +58,13 @@ for x <- xs par:
par continueWithBaz()
```
Contract is changed as in [Conditional](conditional.md#contract) flow.
The contract is changed as in [Conditional](conditional.md#contract) flow.
## Export data from for
### 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
```haskell
xs: []string
return: *string
@ -79,7 +77,7 @@ for x <- xs par:
baz(return!5, return)
```
## For on streams
### `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.
`for` on streams is one of the most advanced and powerful parts of Aqua. See [CRDT streams](../crdt-streams.md) for details.

View File

@ -1,32 +1,31 @@
# Parallel
Parallel execution is where everything becomes shiny.
Parallel execution is where Aqua fully shines.
## 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.
* Parallel arms have no access to each other's data. Sync points must be explicit \(see [Join behavior](parallel.md#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\)
* No parallelism when executing a script on a single peer
* No concurrency in services: every service instance does only one job simultaneously.
* Keep services small in terms of computation and memory \(WebAssembly limitation\)
We might overcome these limitations later, but for now, plan your application design having this in mind.
These limitations might be overcome in future Aqua updates, but for now, plan your application design having this in mind.
## Parallel operations
## par
#### par
`par` syntax is derived from π-calculus notation of parallelism: `A | B`
```text
```haskell
-- foo and bar will be executed in parallel, if possible
foo()
par bar()
@ -52,13 +51,13 @@ hello(x)
par hello(y)
```
`par` works in infix manner between the previously stated function and the next one.
`par` works in an 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.
`co` , short for `coroutine`, prefixes an operation to send it to the background. From π-calculus perspective, it's the same as `A | null`, where `null`-process is the one that does nothing and completes instantly.
```text
```haskell
-- Let's send foo to background and continue
co foo()
@ -86,7 +85,7 @@ Join means that data was created by different parallel execution flows and then
In Aqua, you can refer to previously defined variables. In case of sequential computations, they are available, if execution not failed:
```text
```haskell
-- Start execution somewhere
on peer1:
-- Go to peer1, execute foo, remember x
@ -105,7 +104,7 @@ 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
```haskell
-- Start execution somewhere
on peer1:
-- Go to peer1, execute foo, remember x
@ -124,15 +123,15 @@ 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.
Actually, the script will be executed twice: the first time it will be sent from `peer1`, and the 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.
When execution will get to `baz` for the first time, Aqua VM 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
```haskell
-- Initiate a stream to write into it several times
results: *string

View File

@ -4,21 +4,18 @@ 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.
* Data from the first arm is available in the second branch.
* The second arm is executed if and only if 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.
Any runnable piece of code in Aqua is an arrow from its domain to the codomain.
```text
```haskell
-- Call a function
foo()
@ -32,13 +29,13 @@ y <- Peer.identify()
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.
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, the [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
```haskell
func foo():
-- Will be executed where `foo` was executed
bar()
@ -60,5 +57,5 @@ func foo():
bar()
```
See more in [Topology](../topology.md) section.
See more in the [Topology](../topology.md) section.

View File

@ -1,12 +1,12 @@
# Imports & exports
# Imports And Exports
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.
An Aqua source file has a head and a body. The body contains function definitions, services, types, constants. The header manages what is imported from other files and what is exported.
## Import expression
### Import Expression
The main way to import a file is via `import` expression:
```text
```haskell
import "@fluencelabs/aqua-lib/builtin.aqua"
func foo():
@ -17,9 +17,9 @@ Aqua compiler takes a source directory and a list of import directories \(usuall
Everything defined in the file is imported into the current namespace.
## `Use` expression
### `use` Expression
Use expression makes it possible to import a subset of a file, or to alias the imports to avoid collisions.
The `use` expression makes it possible to import a subset of a file, or to alias imports to avoid namespace collisions.
{% embed url="https://github.com/fluencelabs/aqua/issues/30" caption="" %}

View File

@ -4,9 +4,9 @@ description: Define where the code is to be executed and how to get there
# Topology
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. Hence, topology is at the heart of Aqua.
Aqua lets developers describe the whole distributed workflow in a single script, link data, recover from errors, implement complex patterns like backpressure, and more. Hence, the network topology is at the heart of Aqua.
Topology in Aqua is declarative: You just need to say where a piece of code must be executed, on what peer, and optionally how to get there. he Aqua compiler will add all the required network hops.
Topology in Aqua is declarative: You just need to say where a piece of code must be executed, on what peer, and optionally how to get there. The Aqua compiler will add all the required network hops.
## On expression
@ -57,8 +57,8 @@ func baz():
Take a minute to think about:
* Where `do_foo` is executed?
* Where `bar(1)` is executed?
* Where is `do_foo` executed?
* Where is `bar(1)` executed?
* On what node `bar(2)` runs?
* What about `bar(3)`?
@ -175,7 +175,7 @@ If you pass a service call as a callback, it will be executed locally on the nod
Functions that capture the topologic context of the definition site are planned, not yet there. **Proposed** syntax:
```text
```haskell
func baz():
foo = do (x: u32):
-- Executed there, where foo is called
@ -188,7 +188,7 @@ func baz():
{% embed url="https://github.com/fluencelabs/aqua/issues/183" caption="Issue for adding \`do\` expression" %}
{% hint style="warning" %}
Passing service function calls as arguments is very fragile as it does not track that a service is resolved in the scope of the call. Abilities variance may fix that.
Passing service function calls as arguments is very fragile as it does not track that the service is resolved in the scope of the call. Abilities variance may fix that.
{% endhint %}
## Parallel execution and topology

View File

@ -18,7 +18,7 @@ You can pass booleans \(true, false\), numbers, double-quoted strings as literal
## Products
```python
```haskell
data ProductName:
field_name: string
@ -45,7 +45,7 @@ You can access a distinct value of a collection with `!` operator, optionally fo
Examples:
```text
```haskell
strict_array: []u32
array_of_arrays: [][]u32
element_5 = strict_array!5
@ -71,7 +71,7 @@ The absence of arguments is denoted `-> ()`.That is, this mapping takes no argum
Note that there's no `Unit` type in Aqua: you cannot assign a non-existing result to a value.
```python
```haskell
-- Assume that arrow has type: -> ()
-- This is possible:
@ -85,7 +85,7 @@ x <- arrow()
For convenience, you can alias a type:
```python
```haskell
alias MyAlias = ?string
```
@ -97,7 +97,7 @@ Therefore Aqua follows the structural typing paradigm: if a type contains all th
For arrow types, Aqua checks the variance on arguments and contravariance on the return type.
```text
```haskell
-- We expect u32
xs: *u32
@ -132,7 +132,7 @@ Arrow type `A: D -> C` is a subtype of `A1: D1 -> C1`, if `D1` is a subtype of `
A service type is a product of arrows.
```text
```haskell
service MyService:
foo(arg: string) -> bool
@ -143,7 +143,7 @@ data MyServiceType:
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
```haskell
-- MyFile.aqua
func foo(arg: string) -> bool:

View File

@ -6,7 +6,7 @@ Values in Aqua are backed by VDS \(Verifiable Data Structures\) in the runtime.
That's why values are immutable. Changing the value effectively makes a new one:
```text
```haskell
x = "hello"
y = "world"
@ -22,7 +22,7 @@ More on that in the Security section. Now let's see how we can work with values
Function arguments are available within the whole function body.
```text
```haskell
func foo(arg: i32, log: string -> ()):
-- Use data arguments
bar(arg)
@ -33,9 +33,9 @@ func foo(arg: i32, log: string -> ()):
## Return values
You can assign results of an arrow call to a name, and use this returned value in the code below.
You can assign the results of an arrow call to a name and use this returned value in the code below.
```text
```haskell
-- Imagine a Stringify service that's always available
service Stringify("stringify"):
i32ToStr(arg: i32) -> string
@ -59,7 +59,7 @@ func foo(arg: i32, log: *string):
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
```haskell
-- String literals cannot contain double quotes
-- No single-quoted strings allowed, no escape chars.
foo("double quoted string literal")
@ -83,7 +83,7 @@ bar(-0.2)
In Aqua, you can use a getter to peak into a field of a product or indexed element in an array.
```text
```haskell
data Sub:
sub: string
@ -105,8 +105,8 @@ func foo(e: Example):
Note that the `!` operator may fail or halt:
* If it is called on an immutable collection, it will fail if the collection is shorter and has no given index; you can handle the error with [try](https://github.com/fluencelabs/aqua-book/tree/d54b086ab43f89c9f5622d26a22574a47d0cde19/language/operators/conditional.md#try) or [otherwise](https://github.com/fluencelabs/aqua-book/tree/d54b086ab43f89c9f5622d26a22574a47d0cde19/language/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](https://github.com/fluencelabs/aqua-book/tree/d54b086ab43f89c9f5622d26a22574a47d0cde19/language/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 the error with [try](https://github.com/fluencelabs/aqua-book/tree/4177e00f9313f0e1eb0a60015e1c19a956c065bd/language/operators/conditional.md#try) or [otherwise](https://github.com/fluencelabs/aqua-book/tree/4177e00f9313f0e1eb0a60015e1c19a956c065bd/language/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](https://github.com/fluencelabs/aqua-book/tree/4177e00f9313f0e1eb0a60015e1c19a956c065bd/language/operators/parallel.md#join-behavior).
{% hint style="warning" %}
The `!` operator can currently only be used with literal indices.
@ -116,9 +116,9 @@ We expect to address this limitation soon.
## Assignments
Assignments, `=`, only give a name to a value with applied getter or to a literal.
Assignments, `=`, only give a name to a value with an applied getter or to a literal.
```text
```haskell
func foo(arg: bool, e: Example):
-- Rename the argument
a = arg
@ -132,9 +132,9 @@ func foo(arg: bool, e: Example):
Constants are like assignments but in the root scope. They can be used in all function bodies, textually below the place of const definition. Constant values must resolve to a literal.
You can change the compilation results with overriding a constant but the override needs to be of the same type or subtype.
You can change the compilation results by overriding a constant but the override needs to be of the same type or subtype.
```text
```haskell
-- This flag is always true
const flag = true
@ -156,7 +156,7 @@ By default, everything defined textually above is available below. With some exc
Functions have isolated scopes:
```text
```haskell
func foo():
a = 5
@ -168,7 +168,7 @@ func bar():
[For loop](flow/iterative.md#export-data-from-for) does not export anything from it:
```text
```haskell
func foo():
x = 5
for y <- ys:
@ -181,7 +181,7 @@ func foo():
[Parallel](flow/parallel.md#join-behavior) branches have [no access](https://github.com/fluencelabs/aqua/issues/90) to each other's data:
```text
```haskell
-- This will deadlock, as foo branch of execution will
-- never send x to a parallel bar branch
x <- foo()
@ -191,9 +191,9 @@ par y <- bar(x)
baz(x, y)
```
Recovery branches in [conditional flow](https://github.com/fluencelabs/aqua-book/tree/d54b086ab43f89c9f5622d26a22574a47d0cde19/language/operators/conditional.md) have no access to the main branch as the main branch exports values, whereas the recovery branch does not:
Recovery branches in [conditional flow](https://github.com/fluencelabs/aqua-book/tree/4177e00f9313f0e1eb0a60015e1c19a956c065bd/language/operators/conditional.md) have no access to the main branch as the main branch exports values, whereas the recovery branch does not:
```text
```haskell
try:
x <- foo()
otherwise:
@ -211,7 +211,7 @@ Stream is a special data structure that allows many writes. It has [a dedicated
To use a stream, you need to initiate it at first:
```text
```haskell
-- Initiate an (empty) appendable collection of strings
resp: *string

View File

@ -1,2 +0,0 @@
# Library \(BuiltIns\)

View File

@ -1,12 +0,0 @@
---
description: How to run Aqua
---
# Runtimes
Okay, we have Aqua code. How to run it?
TypeScript, JS, pure AIR.
Reference to AquaVM.

View File

@ -1,2 +0,0 @@
# Aqua VM