Merge branch 'main' into alpha

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

View File

@ -4,9 +4,3 @@
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,2 +1 @@
# Language

View File

@ -4,9 +4,9 @@ While [Execution flow](flow/) organizes the flow from peer to peer, Abilities &
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.
{% embed url="https://github.com/fluencelabs/aqua/issues/33" %}
{% embed url="https://github.com/fluencelabs/aqua/issues/33" caption="" %}
### Services
## Services
A Service interfaces functions \(often provided via WebAssembly interface\) executable on a peer. Example of service definition:
@ -27,41 +27,43 @@ Some services may be singletons available on all peers. Such services are called
-- 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()
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.
```haskell
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 convenient ways to do it is to generate Aqua types from Wasm code in Marine.

View File

@ -20,18 +20,18 @@ Stream is a kind of [collection](types.md#collection-types) and can be used in p
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)
foo(peer, relayMaybe)
```
But the most powerful use of streams pertains to their use with parallel execution, which incurs non-determinism.
@ -41,34 +41,33 @@ But the most powerful use of streams pertains to their use with parallel executi
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 a scalar
* Map: Handles the stream values
* Sink: Converts the resulting stream into a scalar
Consider the following example:
```haskell
func foo(peers: []string) -> string:
resp: *string
-- Go to all peers in parallel
-- 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.
@ -82,4 +81,3 @@ And then the results are sent to the first peer, to call Op.identity there. This
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

@ -2,16 +2,16 @@
Aqua supports branching: you can return one value or another, recover from the error, or check a boolean expression.
### Contract
## Contract
* 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
## Conditional operations
#### try
### try
Tries to perform operations, or swallows the error \(if there's no catch, otherwise after the try block\).
@ -24,7 +24,7 @@ try:
x <- foo()
```
#### catch
### catch
Catches the standard error from `try` block.
@ -44,7 +44,7 @@ data LastError:
peer_id: string -- On what peer the error happened
```
#### if
### if
If corresponds to `match`, `mismatch` extension of π-calculus.
@ -53,21 +53,21 @@ x = true
if x:
-- always executed
foo()
if x == false:
-- never executed
bar()
if x != false:
-- executed
baz()
baz()
```
Currently, you may only use one `==`, `!=` operator in the `if` expression, or compare with true.
Both operands can be variables.
#### else
### else
Just the second branch of `if`, in case the condition does not hold.
@ -75,12 +75,12 @@ Just the second branch of `if`, in case the condition does not hold.
if true:
foo()
else:
bar()
bar()
```
If you want to set a variable based on condition, see Conditional return.
#### otherwise
### otherwise
You may add `otherwise` to provide recovery for any block or expression:
@ -91,7 +91,7 @@ otherwise:
y <- bar()
```
### Conditional return
## Conditional return
In Aqua, functions may have only one return expression, which is very last. And conditional expressions cannot define the same variable:
@ -99,7 +99,7 @@ In Aqua, functions may have only one return expression, which is very last. And
try:
x <- foo()
otherwise:
x <- bar() -- Error: name x was already defined in scope, can't compile
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).
@ -111,7 +111,7 @@ try:
resultBox <- foo()
otherwise:
resultBox <- bar()
-- now result contains only one value, let's extract it!
result = resultBox!

View File

@ -6,6 +6,7 @@ In Aqua, two operations correspond to it: you can call a service function \(it's
### `for` expression
In short, `for` looks like the following:
```haskell
@ -13,13 +14,13 @@ xs: []string
for x <- xs:
y <- foo(x)
-- x and y are not accessible there, you can even redefine them
x <- bar()
y <- baz()
y <- baz()
```
### Contract
## Contract
* Iterations of `for` loop are executed sequentially by default.
* Variables defined inside `for` loop are not available outside.
@ -27,7 +28,7 @@ y <- baz()
* `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).
You can make several trials in a loop, and break once any trial succeeded.
```haskell
@ -50,11 +51,11 @@ 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()
par continueWithBaz()
```
The contract is changed as in [Conditional](conditional.md#contract) flow.
@ -71,9 +72,9 @@ return: *string
for x <- xs par:
on x:
return <- foo()
-- Wait for 6 fastest results -- see Join behavior
baz(return!5, return)
baz(return!5, return)
```
### `for` on streams

View File

@ -2,13 +2,13 @@
Parallel execution is where Aqua fully shines.
### Contract
## Contract
* 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
## Implementation limitation
Parallel execution has some implementation limitations:
@ -19,7 +19,7 @@ Parallel execution has some implementation limitations:
These limitations might be overcome in future Aqua updates, but for now, plan your application design having this in mind.
### Parallel operations
## Parallel operations
#### par
@ -38,7 +38,7 @@ 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
@ -53,7 +53,7 @@ par hello(y)
`par` works in an infix manner between the previously stated function and the next one.
#### co
### co
`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.
@ -64,7 +64,7 @@ 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()
@ -79,7 +79,7 @@ bar()
bax(x)
```
### Join behavior
## 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`\).
@ -90,16 +90,16 @@ In Aqua, you can refer to previously defined variables. In case of sequential co
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)
baz(x, y)
```
Let's make this script parallel: execute `foo` and `bar` on different peers in parallel, then use both to compute `baz`.
@ -109,9 +109,9 @@ Let's make this script parallel: execute `foo` and `bar` on different peers in p
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()

View File

@ -2,16 +2,16 @@
By default, Aqua code is executed line by line, sequentially.
### Contract
## Contract
* 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
## Sequential operations
#### call arrow
### call arrow
Any runnable piece of code in Aqua is an arrow from its domain to the codomain.
@ -31,7 +31,7 @@ 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, 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
`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.
@ -39,19 +39,19 @@ When you write `<-`, this means not just "assign results of the function on the
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()

View File

@ -21,7 +21,5 @@ Everything defined in the file is imported into the current namespace.
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" %}
{% embed url="https://github.com/fluencelabs/aqua/issues/30" caption="" %}

View File

@ -33,7 +33,7 @@ func foo(arg: i32, log: string -> ()):
## Return values
You can assign the 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.
```haskell
-- Imagine a Stringify service that's always available

View File

@ -8,7 +8,7 @@
* chain-forward pattern
* Note on Marine, Wasm IT
Given an abundance of active and abandoned programming languages, why create another one ? The need for Aqua arises from the desire to maximize the potential afforded by peer-to-peer networks as a distributed hosting environment for services composable into applications and backends.
Given an abundance of active and abandoned programming languages, why create another one ? The need for Aqua arises from the desire to maximize the potential afforded by peer-to-peer networks as a distributed hosting environment for services composable into applications and backends.
Figure x: need one new graphic to illustrate both aspects
@ -19,11 +19,11 @@ That is, Aqua provides the capabilities necessary to implement and execute a "fu
* Programmable network requests
* Extensible beyond peer-native services to Web2 resources
At the heart of the peer-to-peer programming model -- is this Fluence or Aquamarine ?
At the heart of the peer-to-peer programming model -- is this Fluence or Aquamarine ?
* _particle_
* _particle_
### A Taste Of Aqua
## A Taste Of Aqua
or a different example?
@ -38,5 +38,3 @@ func greeter(name: string, greet: bool, node: string, service_id: string) -> str
<- res
```