GitBook: [alpha] 30 pages modified

This commit is contained in:
Dmitry Kurinskiy 2021-10-05 07:26:13 +00:00 committed by gitbook-bot
parent bec7e16368
commit 5c46b605e8
No known key found for this signature in database
GPG Key ID: 07D2180C7B12D0FF
11 changed files with 687 additions and 13 deletions

View File

@ -15,8 +15,9 @@
* [Iterative](language/flow/iterative.md)
* [Abilities & Services](language/abilities-and-services.md)
* [CRDT Streams](language/crdt-streams.md)
* [Imports And Exports](language/header/README.md)
* [Control, Scope And Visibility](language/header/control-scope-and-visibility.md)
* [Imports And Exports](language/header.md)
* [Control, Scope And Visibility](language/control-scope-and-visibility/README.md)
* [Control, Scope And Visibility](language/control-scope-and-visibility/control-scope-and-visibility.md)
* [Expressions](language/expressions/README.md)
* [Header](language/expressions/header.md)
* [Functions](language/expressions/functions.md)

View File

@ -34,7 +34,7 @@ Aqua compiler's versioning scheme is the following: `0.BREAKING.ENHANCING.RELEAS
### [0.1.14](https://github.com/fluencelabs/aqua/releases/tag/0.1.14) August 20, 2021
* Aqua file header changes: `module`, `declares`, `use`, `export` expressions \([\#245](https://github.com/fluencelabs/aqua/pull/245)\), see [Imports and Exports](language/header/) for the docs.
* Aqua file header changes: `module`, `declares`, `use`, `export` expressions \([\#245](https://github.com/fluencelabs/aqua/pull/245)\), see [Imports and Exports](language/header.md) for the docs.
* Experimental Scala.js build of the compiler \([\#247](https://github.com/fluencelabs/aqua/pull/247)\)
### [0.1.13](https://github.com/fluencelabs/aqua/releases/tag/0.1.13) August 10, 2021

View File

@ -5,7 +5,7 @@ Both the Aqua compiler and support library can be installed natively with `npm`
To install the compiler:
```bash
npm -g install @fluencelabs/aqua-cli
npm -g install @fluencelabs/aqua
```
and to make the Aqua library available to Typescript applications:

View File

@ -42,12 +42,12 @@ func ts_getter(node: string) -> []u64:
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:
See the [ts-oracle example](https://github.com/fluencelabs/examples/tree/d52f06dfc3d30799fe6bd8e3e602c8ea1d1b8e8a/aqua-examples/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
aqua -i aqua-scripts -o air-scripts -a
```
{% endtab %}

View File

@ -48,7 +48,7 @@ Advanced parallelism:
Code management:
* [Imports & exports](header/)
* [Imports & exports](header.md)
Reference:

View File

@ -0,0 +1,353 @@
# Control, Scope And Visibility
##
In Aqua, the default namespace of a module is the file name and all declarations, i.e., data, services and functions, are public.
For example, a `default.aqua` file:
```text
-- default_foo.aqua
func foo() -> string:
<- "I am a visible foo func that compiles"
```
Provides visibility to all members, i.e. `foo`.
Let's compile the file:
```text
aqua -i aqua-scripts -o compiled-aqua
```
To obtain Typescript wrapped AIR, `default_foo.ts` in the `compiled-aqua` directory:
```text
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
// Services
// Functions
export async function foo(client: FluenceClient, config?: {ttl?: number}): Promise<string> {
let request: RequestFlow;
const promise = new Promise<string>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(xor
(call %init_peer_id% ("callbackSrv" "response") ["I am a visible foo func that compiles"])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return client.relayPeerId!;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for foo');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
await client.initiateFlow(request!);
return promise;
}
```
Regardless of your output target, i.e. AIR or Typescript, the default module namespace is `default_foo` and `foo` is the compiled function. If you are following along, check your `compiled-aqua` directory for the output file.
While the default approach is handy for single file, single module development, it makes for inefficient dependency management. The remainder of this section introduces the scoping and visibility concepts available in Aqua to effectively manage dependencies.
### Visibility In Aqua Exports
By default, all declarations in a module, i.e., _data_, _service_ and _func_, are public. With the `module` declaration, Aqua allows developers to create named modules and define membership visibility where the default visibility of `module` is private and module members do not get compiled.
Let's create an `export.aqua` file to check things out:
```text
-- export.aqua
module Export
func foo() -> string:
<- "I am Export foo"
```
When we compile `export.aqua`
```text
aqua -i aqua-scripts -o compiled-aqua
```
nothing gets compiled as expected:
```text
2021.09.02 11:31:41 [INFO] Aqua Compiler 0.2.1-219
2021.09.02 11:31:42 [INFO] Source /Users/bebo/localdev/aqua-245/documentation-examples/aqua-scripts/export.aqua: compilation OK (nothing to emit)
```
Again, you can check the output directory, `compiled-aqua` , for the lack of output files. By corollary, `foo` cannot be imported from another files. For example:
```text
-- import.aqua
import "module_foo.aqua"
func wrapped_foo() -> string:
res <- foo()
<- res
```
Results in a compile failure since `foo` is not visible to `import.aqua`:
```text
this is not working as expected. Issue filed.
```
In order to make module members available for use by other modules, we can use `declares`. For example,
```text
-- export.aqua
module ExportModule declares foo
func bar() -> string:
<- " I am MyFooBar bar"
func foo() -> string:
res <- bar()
<- res
```
Keeps `bar` private but exposes `foo` as public for use by another module only. That is, compiling just `expert.aqua` does not create output if compiled directly:
```text
2021.09.02 16:33:53 [INFO] Aqua Compiler 0.2.1-219
2021.09.02 16:33:54 [INFO] Source /Users/bebo/localdev/aqua-245/documentation-examples/aqua-scripts/export.aqua: compilation OK (nothing to emit)
```
Let's add a module consuming `foo`:
```text
-- import.aqua
import "export.aqua"
func foo_wrapper() -> string:
res <- foo()
<- res
```
results in the compilation of `foo` and `foo_wrapper`:
```text
2021.09.02 16:37:18 [INFO] Aqua Compiler 0.2.1-219
2021.09.02 16:37:18 [INFO] Source /Users/bebo/localdev/aqua-245/documentation-examples/aqua-scripts/export.aqua: compilation OK (nothing to emit)
2021.09.02 16:37:18 [INFO] Result /Users/bebo/localdev/aqua-245/documentation-examples/compiled-aqua/import.ts: compilation OK (1 functions)
```
Check out the output file, `compiled-aqua/import.ts`:
```text
export async function foo_wrapper(client: FluenceClient, config?: {ttl?: number}): Promise<string> {
let request: RequestFlow;
const promise = new Promise<string>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(xor
(call %init_peer_id% ("callbackSrv" "response") [" I am MyFooBar bar"])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return client.relayPeerId!;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for foo_wrapper');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
await client.initiateFlow(request!);
return promise;
}
```
Of course, if we change `import.aqua` to:
```text
import bar from "export.aqua"
func bar_wrapper() -> string:
res <- bar()
<- res
```
we get the expected error:
```text
import bar from "export.aqua"
^^^===================
Imported file declares [foo], no bar declared. Try adding `declares *` to that file.
```
since `bar` was not declared to be visible outside the module namespace.
As indicated in the error message, `declares *` makes all members of the namespace public.
Note: `exports` isn't working yet
### Scoping Aqua Imports
We already encountered the `import` statement earlier. Using `import` with the file name, e.g., `import "export.aqua"`, imports all namespace members visible for export. However, we can scope import with the `from` modifier, e.g., `import foo from "file.aqua"`, to limit our imports and subsequent compilation outputs.
Moreover, we can associate a namespace with an `import`, e.g., `import foo from "export.aqua" as ExportModule`, which then allows us to use `foo` as:
```text
import foo from "export.aqua" as ExportModule
func demo_foo() -> string:
res <- ExportModule.foo()
<- res
```
and we can rename our imports with the `as` modifier with or without the `module` modifier:
```text
import foo as ex_foo from "export.aqua" as ExportModule
func demo_foo() -> string:
res <- ExportModule.ex_foo()
<- res
```
In addition to `import`, we also have the `use` keyword available to link and scope. The difference between`use` and `import` is that `use` brings in module namespaces declared in the referenced file. For example:
```text
-- export.aqua
module ExportModule declares foo
func foo() -> string:
<- "I am a foo fighter"
```
declares the `ExportModule` namespace and makes `foo` visible. We can now bring `foo` into scope by means of its module namespace `ExportModule` in our import file without having to \(re-\) declare anything:
```text
-- import.aqua
use "export.aqua"
func foo_wrapper() -> string:
res <- ExportModule.foo()
<- res
```
If, for validation purposes, we use:
```text
-- import.aqua
use "export.aqua"
func foo_wrapper() -> string:
res <- foo()
<- res
```
We get the expected error:
```text
3 func foo_wrapper() -> string:
4 res <- foo()
5 <- res
^^^
Undefined name, available: ExportModule.foo, host_peer_id, ExportModule.host_peer_id, ExportModule.%last_error%, ExportModule.nil, nil, %last_error%
```
If we don't declare a module name in our reference file and use `use` in our consumer file, the compiler will throw an error:
```text
-- export.aqua
func foo() -> string:
<- "I am a foo fighter"
```
```text
use "export.aqua"
^^^^^^^^^^^^^
Used module has no `module` header. Please add `module` header or use ... as ModuleName, or switch to import
```
Following the compiler instructions as can declare a module name in the importing file:
```text
use "export.aqua" as ModuleName
func foo_wrapper() -> string:
res <- ModuleName.foo()
<- res
```
Note: also doesn't compile just yet

View File

@ -0,0 +1,316 @@
# Control, Scope And Visibility
In Aqua, the default namespace of a module is the file name and all declarations, i.e., data, services and functions, are public.
For example, the default.aqua file:
```python
-- default_foo.aqua
func foo() -> string:
<- "I am a visible foo func that compiles"
```
Which we compile with
```bash
aqua -i aqua-scripts -o compiled-aqua
```
to obtain Typescript wrapped AIR, `default_foo.ts` in the `compiled-aqua` directory:
```typescript
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
// Services
// Functions
export async function foo(client: FluenceClient, config?: {ttl?: number}): Promise<string> {
let request: RequestFlow;
const promise = new Promise<string>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(xor
(call %init_peer_id% ("callbackSrv" "response") ["I am a visible foo func that compiles"])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return client.relayPeerId!;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for foo');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
await client.initiateFlow(request!);
return promise;
}
```
Regardless of your output target, i.e. raw AIR or Typescript wrapped AIR, the default module namespace is `default_foo` and `foo` is the compiled function.
While this default approach is handy for single file, single module development, it makes for inefficient dependency management and unnecessary compilations for multi-module projects. The remainder of this section introduces the scoping and visibility concepts available in Aqua to effectively manage dependencies.
### Managing Visibility With `module` and `declare`
By default, all declarations in a module, i.e., _data_, _service_ and _func_, are public. With the `module` declaration, Aqua allows developers to create named modules and define membership visibility where the default visibility of `module` is private. That is, with the `module` declaration all module members are private and do not get compiled.
Let's create an `export.aqua` file like so:
```python
module Export
func foo() -> string:
<- "I am Export foo"
```
When we compile `export.aqua`
```bash
aqua -i aqua-scripts -o compiled-aqua
```
nothing gets compiled as expected:
```bash
2021.09.02 11:31:41 [INFO] Aqua Compiler 0.2.1-219
2021.09.02 11:31:42 [INFO] Source /Users/bebo/localdev/aqua-245/documentation-examples/aqua-scripts/export.aqua: compilation OK (nothing to emit)
```
You can further check the output directory, `compiled-aqua`, in our case, for the lack of output files. By corollary, `foo` cannot be imported from another files. For example:
```python
-- import.aqua
import "export.aqua"
func wrapped_foo() -> string:
res <- foo()
<- res
```
Results in compile failure since `foo` is not visible to `import.aqua`:
```python
6 func wrapped_foo() -> string:
7 res <- foo()
^^^==
Undefined arrow, available: HOST_PEER_ID, INIT_PEER_ID, nil, LAST_ERROR
8 <- res
```
We can use `declares` to create visibility for a `module` namespace for **consuming** modules. For example,
```python
-- export.aqua
module Export declares foo
func bar() -> string:
<- " I am MyFooBar bar"
func foo() -> string:
res <- bar()
<- res
```
in and by itself does not result in compiled Aqua:
```bash
aqua -i aqua-scripts -o compiled-aqua -a
Aqua JS: node /Users/bebo/.nvm/versions/node/v14.16.0/lib/node_modules/@fluencelabs/aqua/aqua.js -i aqua-scripts -o compiled-aqua -a
Aqua JS:
Aqua JS: 2021.09.08 13:36:17 [INFO] Aqua Compiler 0.3.0-222
2021.09.08 13:36:21 [INFO] Source /Users/bebo/localdev/aqua-245/documentation-examples/aqua-scripts/export.aqua: compilation OK (nothing to emit)
```
But once we link from another module, e.g.:
```python
import foo from "export.aqua"
func foo_wrapper() -> string:
res <- foo()
<- res
```
We get the appropriate result:
```bash
2021.09.08 13:40:17 [INFO] Source /Users/bebo/localdev/aqua-245/documentation-examples/aqua-scripts/export.aqua: compilation OK (nothing to emit)
2021.09.08 13:40:17 [INFO] Result /Users/bebo/localdev/aqua-245/documentation-examples/compiled-aqua/import.ts: compilation OK (1 functions)
```
in form of `import.ts`:
```typescript
// compiled-aqua/import.ts
import { FluencePeer } from '@fluencelabs/fluence';
import {
ResultCodes,
RequestFlow,
RequestFlowBuilder,
CallParams,
} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1';
// Services
// Functions
export function foo_wrapper(config?: {ttl?: number}) : Promise<string>;
export function foo_wrapper(peer: FluencePeer, config?: {ttl?: number}) : Promise<string>;
export function foo_wrapper(...args) {
let peer: FluencePeer;
let config;
if (args[0] instanceof FluencePeer) {
peer = args[0];
config = args[1];
} else {
peer = FluencePeer.default;
config = args[0];
}
let request: RequestFlow;
const promise = new Promise<string>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(xor
(call %init_peer_id% ("callbackSrv" "response") [" I am MyFooBar bar"])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.connectionInfo.connectedRelay ;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for foo_wrapper');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
```
Of course, if we change `import.aqua` to include the private `bar`:
```python
import bar from "export.aqua"
func bar_wrapper() -> string:
res <- bar()
<- res
```
We get the expected error:
```python
import bar from "export.aqua"
^^^===================
Imported file declares [foo], no bar declared. Try adding `declares *` to that file.
```
As indicated in the error message, `declares *` makes all members of the namespace public, although we can be quite fine-grained and use a comma separated list of members we want to be visible, such as `declares foo, bar`.
### Scoping Inclusion With `use` and `import`
We already encountered the `import` statement earlier. Using `import` with the file name, e.g., `import "export.aqua"`, imports all visible, i.e., public, members from the dependency. We can manage import granularity with the `from` modifier, e.g., `import foo from "file.aqua"`, to limit our imports and subsequent compilation outputs. Moreover, we can alias imported declarations with the `as` modifier, e.g.,`import foo as HighFoo, bar as LowBar from "export_file.aqua"`.
In addition to `import`, we also have the `use` keyword available to link and scope. The difference between`use` and `import` is that `use` brings in module namespaces declared in the referenced source file. For example:
```python
-- export.aqua
module ExportModule declares foo
func foo() -> string:
<- "I am a foo fighter"
```
declares the `ExportModule` namespace and makes `foo` visible. We can now bring `foo` into scope by means of its module namespace `ExportModule` in our import file without having to \(re-\) declare anything:
```python
-- import.aqua
use "export.aqua"
func foo -> string:
res <- ExportModule.foo()
<- res
```
This example already illustrates the power of `use` as we now can declare a local `foo` function rather than the `foo_wrapper` we used earlier. `use` provides very clear namespace separation that is fully enforced at the compiler level allowing developers to build, update and extend complex code bases with clear namespace separation by default.
The default behavior for `use` is to use the dependent filename if no module declaration was provided. Moreover, we can use the `as` modifier to change the module namespace. Continuing with the above example:
```python
-- import.aqua
use "export.aqua" as RenamedExport
func foo() -> string:
-- res <- ExportModule.foo() --< this fails
res <- RenamedExport.foo()
<- res
```

View File

@ -12,5 +12,5 @@ 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.
See [Imports & Exports](../header/) for details.
See [Imports & Exports](../header.md) for details.

View File

@ -43,12 +43,16 @@ func foo():
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.
{% hint style="info" %}
`.aqua` extension in `import` and `use` expressions can be ommited. So, `import "builtin.aqua"` does exactly the same as `import "builtin"`.
{% endhint %}
Everything defined in the file is imported into the current namespace.
You can cherry-pick and rename imports using `import ... from` expression:
```python
import Op as Noop from "@fluencelabs/aqua-lib/builtin.aqua"
import Op as Noop from "@fluencelabs/aqua-lib/builtin"
func foo():
Noop.noop()
@ -59,7 +63,7 @@ func foo():
The `use` expression makes it possible to import a file into a named scope.
```python
use Op from "@fluencelabs/aqua-lib/builtin.aqua" as BuiltIn
use Op from "@fluencelabs/aqua-lib/builtin" as BuiltIn
func foo():
BuiltIn.Op.noop()
@ -84,7 +88,7 @@ Another problem is libraries distribution. If a developer wants to deliver an `.
`export` lets a developer decide what exactly is going to be exported, including imported functions.
```python
import bar from "lib.aqua"
import bar from "lib"
-- Exported functions and services will be compiled for the host language
-- You can use several `export` expressions

View File

@ -166,7 +166,7 @@ data MyServiceType:
flag: bool
```
See [Imports and Exports](header/#module) for module declarations.
See [Imports and Exports](header.md#module) for module declarations.
{% embed url="https://github.com/fluencelabs/aqua/blob/main/types/src/main/scala/aqua/types/Type.scala" caption="See the types system implementation" %}

View File

@ -34,7 +34,7 @@ After running `npm i`, you will have `@fluencelabs/aqua-lib` in `node_modules`
### In Aqua
After the library is downloaded, you can import it in your `.aqua` script as documented in [Imports And Exports](../language/header/):
After the library is downloaded, you can import it in your `.aqua` script as documented in [Imports And Exports](../language/header.md):
```javascript
import "@fluencelabs/aqua-lib/builtin.aqua"