DOCS: A whole bunch of improvements.

* Changed the ordering so that tutorials come first.
* Added a tutorial on recursive modules.
This commit is contained in:
Jeremy Wall 2019-02-25 21:02:26 -06:00
parent 7ae6955066
commit f691489bf8
7 changed files with 178 additions and 13 deletions

View File

@ -85,23 +85,28 @@ tbody td:nth-child(even) {
<thead><th></th><th>UCG</th><th>JSonnet</th><th>Dhall</th></thead>
<tr>
<td><b>Variables</b></td><td>x</td> <td>x</td><td>x</td>
<tr>
</tr>
<tr>
<td><b>Conditionals</b></td><td>x</td><td>x</td><td>x</td>
<tr>
</tr>
<tr>
<td><b>Functions</b></td><td>x</td><td>x</td><td>x</td>
<tr>
</tr>
<tr>
<td><b>Modules/Classes</b></td><td>x</td><td>x</td><td>x</td>
<tr>
</tr>
<td><b>Imports</b></td><td>x</td><td>x</td><td>x</td>
<tr>
<td><b>Imports</b></td><td>x</td><td>x</td><td>x</td>
</tr>
<tr>
<td><b>Std Lib</b></td><td>Minimal</td><td>x</td><td>Prelude</td>
</tr>
<tr><td><b>Type Safety</b></td><td>Inferred types and schema validation<td>Unknown</td><td>Inferred types</td></tr>
<tr>
<td><b>Type Safety</b></td><td>Inferred types and schema validation<td>Unknown</td><td>Inferred types</td>
</tr>
<tr>
<td><b>Guaranteed to terminate</b></td><td>No</td><td>No</td><td>Yes</td>
</tr>
</table>
<table>

View File

@ -1,7 +1,7 @@
+++
title = "The UCG Language Reference"
slug = "reference"
weight = 2
weight = 3
sort_by = "weight"
in_search_index = true
+++

View File

@ -1,5 +1,5 @@
+++
title = "UCG Converters"
title = "Converters"
weight = 5
sort_by = "weight"
in_search_index = true

View File

@ -1,7 +1,7 @@
+++
title = "The UCG Standard Library"
slug = "stdlib"
weight = 3
weight = 4
sort_by = "weight"
in_search_index = true
+++

View File

@ -1,12 +1,13 @@
+++
title = "HowTo Guides"
weight = 4
title = "Tutorials"
weight = 2
sort_by = "weight"
in_search_index = true
+++
A collection of useful HowTo Guides
A collection of useful Tutorials
----------
Some example use cases for UCG.
* <a href="script">Creating a launch script for a docker container.</a>
* <a href="script">Creating a launch script for a docker container</a>
* <a href="recursive-modules">Recursive Modules</a>

View File

@ -0,0 +1,159 @@
+++
title = "Recursive Modules and their Uses"
slug = "recursive-modules"
weight = 2
in_search_index = true
+++
Modules are the only expression in UCG with the capablility to perform
recursion using self import[^1]. This is safe because statements in a module
are evaluated at module copy time. This is useful when you need to traverse a
UCG value deeply. You can see an example of this in the UCG `std/schema.ucg`
module where we perform a deep type comparison between two UCG values.
As an example we will create a module that can do a dynamic deep index into a
tuple by using recursion. The module will take in a tuple to traverse and a
list of field names to descend into.
Create a file called `deep_index.ucg` and put the following into it.
```
let deep_index = module {
tpl = {}, // We expect that tuples here.
names = [], // We expect a list of names here.
} => {
// todo
};
```
Our `deep_index` module expects a tuple and list of names. We set the defaults
to an empty tuple and an empty list so anything that isn't either those or NULL
will be a compile error when we call our module. Next we need to check for the
existence of the first name in the tuple.
```
let deep_index = module {
tpl = {}, // We expect tuples here.
names = [], // We expect a list of names here.
} => (result) {
// Fill in
let result = select ((mod.names.0) in mod.tpl), NULL, {
true = mod.tpl.(mod.names.0),
};
};
```
There are a few things to note here. We use the out expression `(result)` to
make this module more ergonomic. We reference our modulr arguments with the
`mod.` prefix. We also surround part of our selector with parens in
`(mod.tpl.(mod.names.0))` to force the last selector to pick the field
dynamically from the result of the grouped expression.
As written this module has a few problems though.
* It is unsafe for inputs where the list of names is empty
* it is unsafe for inputs where the list of names contains items that are not
strings.
## Handling Unsafe inputs.
We want it to be a compile error if anyone calls our module with inputs of the
wrong shape. We have two possibilities to handle. NULL inputs for either
argument or an list that contains items that are not strings.
```
let deep_index = module {
tpl = {}, // We expect tuples here.
names = [], // We expect a list of names here.
} => (result) {
// import some useful items from the std lib
let schema = import "std/schema.ucg";
let l = import "std/lists.ucg";
// Assert some compile time expectations.
// First ensure that names is a list of strings.
(names != NULL && schema.shaped{val=names, shape=[""]}) ||
fail "names must be non NULL and a list of strings";
// Now ensure that tpl is not NULL
tpl != NULL || fail "tpl must not be NULL";
let select_field = func(name) select (name in mod.tpl), NULL {
true = mod.tpl.(name),
};
let result = select l.len(names) > 0, mod.tpl, {
true = select_field(mod.names.0),
};
};
```
Our module will now throw appropriate compile time errors if we pass in invalid
input shapes. We also correctly handle the case where we have no more names to
descend into. We now need to handle calling our module recursively.
## Module Recursion
In order to do recursion we have to import ourself.
```
let deep_index = module {
tpl = {}, // We expect that tuples here.
names = [], // We expect a list of names here.
} => (result) {
// import some useful items from the std lib
let schema = import "std/schema.ucg";
let l = import "std/lists.ucg";
// import ourself now.
let self = import "deep_index.ucg".deep_index;
// Assert some compile time expectations.
// First ensure that names is a list of strings.
(names != NULL && schema.shaped{val=names, shape=[""]}) ||
fail "names must be non NULL and a list of strings";
// Now ensure that tpl is not NULL
tpl != NULL || fail "tpl must not be NULL";
let name = mod.names.0;
let fail_msg = "Attempt to descend into a non existent name @" % (name);
// Field selector function that throws a compile error if the field
// doesn't exist.
let select_field = func(name) => select (name in mod.tpl), fail fail_message {
true = mod.tpl.(name),
};
// calculate our next value.
// If there are no more names then something has gone wrong and
// we should hard fail.
let next = select l.len(names) > 0, mod.tpl, fail "Impossible" {
true = select_field(name),
};
// we need to get the tail of our list.
let tail = l.tail(names);
// if the tail is non empty then recurse down.
// If the tail is empty then return the current value.
let result = select l.len(tail) > 0, next, {
// recurse
true = self{tpl=result, names=tail},
};
};
```
Our `deep_index` module can now descend down into a tuple given a list of field
names. If any of the names does not exist it will be a compile failure.
## Final notes
One consquence of this method of doing recursion is that you can not reference
the module in the file it is defined in. Doing so would result in a compile
error as UCG detects an import loop. In practice this does not prove to be a
problem since nearly all usages of the module will be outside of the file it is
defined in.
<hr>
[^1]: Technnically functions can do recursion if you manually pass in the same function as an argument to itself, but this is unwieldy and error-prone.