mirror of
https://github.com/fluencelabs/aqua-book
synced 2024-12-04 15:20:19 +00:00
GitBook: [bb-edit] 31 pages modified
This commit is contained in:
parent
d80d66b7a7
commit
350cf9023e
@ -1,13 +1,2 @@
|
||||
# Language
|
||||
|
||||
* Turing incomplete
|
||||
* strong typing
|
||||
* functional
|
||||
* theoretical foundation
|
||||
|
||||
|
||||
|
||||
see start: [https://hackmd.io/@k0-nEQfXQsuo0LE3OblOjg/HJ1DoCdIu](https://hackmd.io/@k0-nEQfXQsuo0LE3OblOjg/HJ1DoCdIu)
|
||||
|
||||
|
||||
|
||||
|
@ -3,15 +3,13 @@
|
||||
Aqua is an opinionated domain-specific language with colons and indentation.
|
||||
|
||||
```text
|
||||
-- Comments begin with double-dash, ends on the end of line
|
||||
-- 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. 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.
|
||||
|
||||
Yielding is denoted by an arrow pointing to the left.
|
||||
Values in Aqua have types, which re 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 `<-`.
|
||||
|
||||
```text
|
||||
-- Define a function that yields a string
|
||||
|
@ -1,10 +1,10 @@
|
||||
# Functions
|
||||
|
||||
Function in Aqua is a block of code expressing a workflow, a coordination scenario that works across many peers.
|
||||
A function in Aqua is a block of code expressing a workflow, i.e., a coordination scenario that works across one or more peers.
|
||||
|
||||
A function may take arguments of any type and may return a value.
|
||||
|
||||
A function can call other functions, take functions as arguments of [arrow type](../types.md#arrow-types), be provided as an arrow argument.
|
||||
A function can call other functions, take functions as arguments of [arrow type](../types.md#arrow-types) and be provided as an arrow argument.
|
||||
|
||||
Essentially, a function is an arrow. The function call is an expression that connects named arguments to an arrow, and gives a name to the result.
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
|
||||
`import`
|
||||
|
||||
Import expression brings everything defined within the imported file into the scope.
|
||||
The `import` expression brings everything defined within the imported file into the scope.
|
||||
|
||||
```text
|
||||
import "path/to/file.aqua"
|
||||
```
|
||||
|
||||
Imported file is first resolved relative to the source file path, then in `-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.
|
||||
|
||||
|
@ -10,7 +10,7 @@ description: Static configuration pieces that affect compilation
|
||||
|
||||
Constant definition.
|
||||
|
||||
Constants can be used all across the functions, exported and imported. If a constant is defined using `?=` , it can be overriden by-value via compiler flags or imported values.
|
||||
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
|
||||
-- This can be overriten with -const "target_peer_id = \"other peer id\""
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
Service definition.
|
||||
|
||||
Service is a program running on a peer. Every service has an interface that consists of a list of functions. To call a service, the service must be identified: so, every service has an ID that must be resolved in the peer scope.
|
||||
A service is a program running on a peer. Every service has an interface that consists of a list of functions. To call a service, the service must be identified: so, every service has an ID that must be resolved in the peer scope.
|
||||
|
||||
In the service definition, you enumerate all the functions, their names, argument, and return types, and optionally provide the default Service ID.
|
||||
|
||||
Services that are a part of protocol come along with IDs. Example of predefined service:
|
||||
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
|
||||
service Peer("peer"):
|
||||
|
@ -16,7 +16,7 @@ Block is considered failed iff the second branch fails to execute.
|
||||
|
||||
#### try
|
||||
|
||||
Tries to perform operations, or swallows the error \(if there's no catch, otherwise after the try block\).
|
||||
Tries to perform operations or consumes the error if there's no catch or otherwise statement after the try block.
|
||||
|
||||
```text
|
||||
try:
|
||||
@ -66,7 +66,7 @@ if x != false:
|
||||
baz()
|
||||
```
|
||||
|
||||
Currently, you may only use one `==`, `!=` operator in the `if` expression, or compare with true.
|
||||
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.
|
||||
|
||||
@ -96,7 +96,7 @@ otherwise:
|
||||
|
||||
### Conditional return
|
||||
|
||||
In Aqua, functions may have only one return expression, which is very last. And conditional expressions cannot define the same variable:
|
||||
In Aqua, functions may have only one return expression, which is on the last line of the function block, and conditional expressions cannot define the same variable:
|
||||
|
||||
```text
|
||||
try:
|
||||
|
@ -15,8 +15,8 @@ All the data defined in parallel branches is available in the subsequent code.
|
||||
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\)
|
||||
* No parallelism when executing a script on single peer \(although a fix is planned\)
|
||||
* No concurrency in services: one service instance does only one job in a time. Keep services small \(Wasm limitation\)
|
||||
|
||||
We might overcome these limitations later, but for now, plan your application design having this in mind.
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
# Sequential
|
||||
|
||||
By default, Aqua code is executed line by line, sequentially.
|
||||
By default, Aqua code is executed sequentially line by line.
|
||||
|
||||
### Contract
|
||||
|
||||
Data from the first branch is available in the second branch.
|
||||
|
||||
Second branch is executed iff the first branch succeeded.
|
||||
Second branch is executed iff the first branch executed successfully.
|
||||
|
||||
If any branch errored, then the whole sequence is errored.
|
||||
|
||||
|
@ -1,26 +1,26 @@
|
||||
---
|
||||
description: Define where the code is to be executed & how to get there
|
||||
description: Define where the code is to be executed and 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.
|
||||
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.
|
||||
|
||||
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 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.
|
||||
|
||||
### On expression
|
||||
|
||||
`on` expression moves execution to the peer:
|
||||
`on` expression moves execution to the specified peer:
|
||||
|
||||
```haskell
|
||||
on "my peer":
|
||||
foo()
|
||||
```
|
||||
|
||||
Foo is instructed to be executed on a peer with id `my peer`. `on` supports variables of type `string` :
|
||||
Here, `foo` is instructed to be executed on a peer with id `my peer`. `on` supports variables of type `string` :
|
||||
|
||||
```haskell
|
||||
-- Foo, bar, baz are instructed to be executed on myPeer
|
||||
-- foo, bar, baz are instructed to be executed on myPeer
|
||||
on myPeer:
|
||||
foo()
|
||||
bar()
|
||||
@ -29,10 +29,10 @@ on myPeer:
|
||||
|
||||
### `%init_peer_id%`
|
||||
|
||||
There's one custom peer ID that's always available in scope: `%init_peer_id%`. It points to the peer who initiated this request.
|
||||
There is one custom peer ID that is always in scope: `%init_peer_id%`. It points to the peer that initiated this request.
|
||||
|
||||
{% hint style="warning" %}
|
||||
But using `on %init_peer_id%` is an anti-pattern: there's no way to ensure that init peer is accessible from the part of the network you're currently in.
|
||||
Using `on %init_peer_id%` is an anti-pattern: There is no way to ensure that init peer is accessible from the currently used part of the network.
|
||||
{% endhint %}
|
||||
|
||||
### More complex scenarios
|
||||
@ -67,22 +67,22 @@ Declarative topology definition always works the same way.
|
||||
* `do_foo` is executed on "peer foo", always.
|
||||
* `bar(1)` is executed on the same node where `baz` was running. If `baz` is the first called function, then it's `init peer id`.
|
||||
* `bar(2)` is executed on `"peer baz"`, despite the fact that foo does topologic transition. `bar(2)` is in the scope of `on "peer baz"`, so it will be executed there
|
||||
* `bar(3)` is executed where `bar(1)` was: in the root scope of `baz`, wherever it was called
|
||||
* `bar(3)` is executed where `bar(1)` was: in the root scope of `baz`, wherever it was called from
|
||||
|
||||
### Accessing peers `via` other peers
|
||||
|
||||
In the distributed network it's a very common situation when a peer is only accessible indirectly. For example, a browser: it has no public network interface, you cannot open a socket to a browser at will. This leads to a `relay` pattern: there should be a well-connected peer that relays requests from a peer to the network and vice versa.
|
||||
In a distributed network it is quite common that a peer is not directly accessible. For example, a browser has no public network interface and you cannot open a socket to a browser at will. Such constraints warrant a `relay` pattern: there should be a well-connected peer that relays requests from a peer to the network and vice versa.
|
||||
|
||||
Relays are handled with `via`:
|
||||
|
||||
```haskell
|
||||
-- When we go to some peer and from some peer,
|
||||
-- compiler will add an additional hop to some relay
|
||||
-- When we go to some peer from some other peer,
|
||||
-- the compiler will add an additional hop to some relay
|
||||
on "some peer" via "some relay":
|
||||
foo()
|
||||
|
||||
-- More complex path: first go to relay2, then to relay1,
|
||||
-- then to peer. When going out of peer, do it reversed way
|
||||
-- then to peer. When going out of peer, do it in reverse
|
||||
on "peer" via relay1 via relay2:
|
||||
foo()
|
||||
|
||||
@ -94,7 +94,7 @@ func doViaRelayMaybe(peer: string, relayMaybe: ?string):
|
||||
foo()
|
||||
```
|
||||
|
||||
Nested `on`s, or delegated in functions, should work just as you expect:
|
||||
`on`s nested or delegated in functions work just as you expect:
|
||||
|
||||
```haskell
|
||||
-- From where we are, -> relay1 -> peer1
|
||||
@ -114,7 +114,7 @@ foo()
|
||||
|
||||
### 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? For example, send a request to a bunch of services and then render the responses as they come.
|
||||
|
||||
This can be done with callback arguments in the entry function:
|
||||
|
||||
@ -149,15 +149,15 @@ 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.
|
||||
Lambda functions that capture the topologic context of the definition site are planned but not implemented yet.
|
||||
|
||||
{% 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.
|
||||
Passing service function calls as arguments is very fragile as it does not track that a service is resolved in the scope of calling. Abilities variance may fix that.
|
||||
{% endhint %}
|
||||
|
||||
### 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 is not always necessary to resolve the 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.
|
||||
|
@ -23,11 +23,11 @@ data ProductName:
|
||||
field_name: string
|
||||
|
||||
data OtherProduct:
|
||||
prod: ProductName
|
||||
product: ProductName
|
||||
flag: bool
|
||||
```
|
||||
|
||||
Fields are accessible with the `.` operator, e.g. `product.field`.
|
||||
Fields are accessible with the dot operator `.` , e.g. `product.field`.
|
||||
|
||||
### Collection Types
|
||||
|
||||
@ -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 prepended with a quantifier, e.g. `*u32`, `[][]string`, `?ProductType` are all correct type specifications.
|
||||
|
||||
You can access a distinct value of a collection with `!` operator, optionally followed by an index.
|
||||
|
||||
@ -65,11 +65,11 @@ 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, the absence of a result is denoted with `()`, e.g., `string -> ()`
|
||||
|
||||
The absence of arguments is denoted by nothing: `-> ()` this arrow takes nothing as an argument and has no return type.
|
||||
The absence of arguments is denoted `-> ()`.That is, this mapping takes no argument and has no return type.
|
||||
|
||||
Note that there's no `Unit` type in Aqua: you cannot assign the non-existing result to a value.
|
||||
Note that there's no `Unit` type in Aqua: you cannot assign a non-existing result to a value.
|
||||
|
||||
```python
|
||||
-- Assume that arrow has 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
|
||||
@ -93,13 +93,13 @@ 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`. The same holds for products.
|
||||
|
||||
For arrow types, Aqua checks variance on arguments, contravariance on the return type.
|
||||
For arrow types, Aqua checks the variance on arguments and contravariance on the return type.
|
||||
|
||||
### Type of a Service and a file
|
||||
### 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. 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. See the [type system implementation](https://github.com/fluencelabs/aqua/blob/main/types/src/main/scala/aqua/types/Type.scala) for more detail.
|
||||
|
||||
{% embed url="https://github.com/fluencelabs/aqua/blob/main/types/src/main/scala/aqua/types/Type.scala" caption="See the types system implementation" %}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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 combining data and computations. The runtime for the compiled Aqua code, [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.
|
||||
|
||||
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\).
|
||||
|
||||
@ -49,7 +49,7 @@ Aqua supports just a few literals: numbers, quoted strings, booleans. You [canno
|
||||
-- String literals cannot contain double quotes
|
||||
foo("double quoted string literal")
|
||||
|
||||
-- Booleans are true and false
|
||||
-- Booleans are true or false
|
||||
if x == false:
|
||||
foo("false is a literal")
|
||||
|
||||
@ -88,16 +88,20 @@ func foo(e: Example):
|
||||
bar(e.arr!2.sub) -- string
|
||||
```
|
||||
|
||||
Note that `!` operator may fail or halt:
|
||||
Note that the `!` 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 immutable collection, it will fail if the 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).
|
||||
|
||||
`!` operator cannot be used with non-literal indices: you can use `!2`, but not `!x`. It's planned to be fixed.
|
||||
{% hint style="warning" %}
|
||||
The `!` operator can currently only be used with literal indices.
|
||||
That is,`!2` is valid but`!x` is not valid.
|
||||
We expect to address this limitation soon.
|
||||
{% endhint %}
|
||||
|
||||
### Assignments
|
||||
|
||||
Assignments do nothing new, just gives a name to a value with applied getter, or name a literal.
|
||||
Assignments, `=`, only give a name to a value with applied getter or to a literal.
|
||||
|
||||
```text
|
||||
func foo(arg: bool, e: Example):
|
||||
@ -111,11 +115,9 @@ func foo(arg: bool, e: Example):
|
||||
|
||||
### 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. 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. Override should be of the same type \(or a subtype\).
|
||||
|
||||
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.
|
||||
|
||||
```text
|
||||
-- This flag is always true
|
||||
@ -174,7 +176,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](operators/conditional.md) have no access to the main branch as the main branch exports values, whereas the recovery branch does not:
|
||||
|
||||
```text
|
||||
try:
|
||||
@ -191,12 +193,12 @@ willFail(y)
|
||||
|
||||
### Streams as literals
|
||||
|
||||
Stream is a special data that allows many writes. It has [a dedicated article](crdt-streams.md).
|
||||
Stream is a special data structure that allows many writes. It has [a dedicated article](crdt-streams.md).
|
||||
|
||||
To use a stream, you need to initiate it at first:
|
||||
|
||||
```text
|
||||
-- Appendable collection of strings, empty
|
||||
-- Initiate an (empty) appendable collection of strings
|
||||
resp: *string
|
||||
|
||||
-- Write strings to resp in parallel
|
||||
|
Loading…
Reference in New Issue
Block a user