Multi-Layered Functions
-
A system is divided into separate layers, each providing logic in terms of scripted functions.
-
A lower layer provides default implementations of certain functions.
-
Higher layers each provide progressively more specific implementations of the same functions.
-
A more specific function, if defined in a higher layer, always overrides the implementation in a lower layer.
-
This is akin to object-oriented programming but with functions.
-
This type of system is extremely convenient for dynamic business rules configuration, setting corporate-wide policies, granting permissions for specific roles etc. where specific, local rules need to override corporate-wide defaults.
Assuming a LOB (line-of-business) system for a large MNC (multi-national corporation) with branches, facilities and offices across the globe.
The system needs to provide basic, corporate-wide policies to be enforced through the worldwide organization, but also cater for country- or region-specific rules, practices and regulations.
Layer | Description |
---|---|
corporate | corporate-wide policies |
regional | regional policy overrides |
country | country-specific modifications for legal compliance |
office | special treatments for individual office locations |
-
Each layer is a separate script.
-
The lowest layer script is compiled into a base
AST
. -
Higher layer scripts are also compiled into
AST
and combined into the base usingAST::combine
(or the+=
operator), overriding any existing functions.
Examples
Assume the following four scripts, one for each layer:
┌────────────────┐
│ corporate.rhai │
└────────────────┘
// Default implementation of 'foo'.
fn foo(x) { x + 1 }
// Default implementation of 'bar'.
fn bar(x, y) { x + y }
// Default implementation of 'no_touch'.
fn no_touch() { throw "do not touch me!"; }
┌───────────────┐
│ regional.rhai │
└───────────────┘
// Specific implementation of 'foo'.
fn foo(x) { x * 2 }
// New implementation for this layer.
fn baz() { print("hello!"); }
┌──────────────┐
│ country.rhai │
└──────────────┘
// Specific implementation of 'bar'.
fn bar(x, y) { x - y }
// Specific implementation of 'baz'.
fn baz() { print("hey!"); }
┌─────────────┐
│ office.rhai │
└─────────────┘
// Specific implementation of 'foo'.
fn foo(x) { x + 42 }
Load and combine them sequentially:
let engine = Engine::new();
// Compile the baseline layer.
let mut ast = engine.compile_file("corporate.rhai".into())?;
// Combine the first layer.
let lowest = engine.compile_file("regional.rhai".into())?;
ast += lowest;
// Combine the second layer.
let middle = engine.compile_file("country.rhai".into())?;
ast += middle;
// Combine the third layer.
let highest = engine.compile_file("office.rhai".into())?;
ast += highest;
// Now, 'ast' contains the following functions:
//
// fn no_touch() { // from 'corporate.rhai'
// throw "do not touch me!";
// }
// fn foo(x) { x + 42 } // from 'office.rhai'
// fn bar(x, y) { x - y } // from 'country.rhai'
// fn baz() { print("hey!"); } // from 'country.rhai'