Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dynamic Modules

Graphix programs can dynamically load modules at runtime. The loaded code will be compiled, type checked, and the loader will return an error indicating any failure in that process. Because Graphix is a statically typed language we must know ahead of time what interface the dynamically loaded module will have. We do this by defining a module signature. We can also define what the dynamically loaded module is allowed to reference, in order to prevent it from just calling any function it likes (aka it's sandboxed). Lets dive right in with an example,

// the module source, which we will publish in netidx
let path = "/local/foo";
let source = "
    let add = |x| x + 1;
    let sub = |x| x - 1;
    let cfg = \[1, 2, 3, 4, 5\];
    let hidden = 42
";
net::publish(path, source)$;

// now load the module
let status = mod foo dynamic {
    sandbox whitelist [core];
    sig {
        val add: fn(i64) -> i64;
        val sub: fn(i64) -> i64;
        val cfg: Array<i64>
    };
    source cast<string>(net::subscribe(path)$)$
};
select status {
    error as e => never(dbg(e)),
    null as _ => foo::add(foo::cfg[0]$)
}

running this we get,

$ graphix test.gx
2

In the first part of this program we just publish a string containing the source code of the module we want to ultimately load. The second part is where it gets interesting, lets break it down.

mod foo dynamic declares a dynamically loaded module named foo. In the rest of our code we can refer (statically) to foo as if it was a normal module that we loaded at compile time. There are three sections required to define a dynamic module, they are required to be defined in order, sandbox, sig, and source,

  • a sandbox statement, of which there are three types
    • sandbox unrestricted; no sandboxing, the dynamic module can access anything in it's scope
    • sandbox whitelist [item0, item1, ...] the dynamic module may access ONLY the names explicitly listed. e.g. sandbox whitelist [core::array]; would allow the dynamic module to access only core::array and nothing else.
    • sandbox blacklist [item0, item1, ...] the dynamic module may access anything except the names listed. sandbox blacklist [super::secret::module]; everything except super secret module would be accessible
  • a sig statement is the type signature of the module. This is a special syntax for writing module type signatures. There are three possible statements,
    • a val statement defines a value and it's type, val add: fn(i64) -> i64 is an example of a val statement, it need not be a function it can be any type
    • a type statement defines a type in the loaded module, e.g. type T = { foo: string, bar: string } val statements that come after a type statement may use the defined type. The type statement is identical to the normal type statement in Graphix (so it can be polymorphic, recursive, etc).
    • a mod statement defines a sub module of the dynamically loaded module. A sub module must have a sig. mod m: sig { ... } defines a sub module.
  • a source statement defines where the source code for the dynamic module will come from. It's type must be a string.

The mod foo dynamic ... expression returns a value of type,

[null, Error<`DynamicLoadError(string)>]

The runtime will try to load the module every time the source updates. If it succeeds it will update with null, if it fails it will update with an error indicating what went wrong. Regardless of the outcome the previous loaded module, if any, will be deleted. If compilation succeeded the new module will be initialized, possibly causing values it exports to update.

Obviously the loaded module must match the type signature defined in the dynamic mod statement. However, the signature checking only cares that every item mentioned in the signature is present in the dynamic module and that the types match. If extra items are present in the dynamic module they will simply be ignored, and will be inaccessible to the loading program.