Add docs for auto-currying.
This commit is contained in:
parent
1465ba2315
commit
8299adf95c
@ -79,6 +79,7 @@ The Rhai Scripting Language
|
||||
4. [Function Pointers](language/fn-ptr.md)
|
||||
5. [Anonymous Functions](language/fn-anon.md)
|
||||
6. [Currying](language/fn-curry.md)
|
||||
7. [Capturing External Variables](language/fn-closure.md)
|
||||
16. [Print and Debug](language/print-debug.md)
|
||||
17. [Modules](language/modules/index.md)
|
||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||
|
@ -15,6 +15,17 @@ The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restri
|
||||
a script to expressions only.
|
||||
|
||||
|
||||
Unicode Standard Annex #31 Identifiers
|
||||
-------------------------------------
|
||||
|
||||
Variable names and other identifiers do not necessarily need to be ASCII-only.
|
||||
|
||||
The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow variable names and identifiers
|
||||
that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
|
||||
|
||||
This is sometimes useful in a non-English DSL.
|
||||
|
||||
|
||||
Disable Keywords and/or Operators
|
||||
--------------------------------
|
||||
|
||||
|
@ -49,9 +49,13 @@ fn anon_fn_1001(x) { this.data -= x; }
|
||||
fn anon_fn_1002() { print this.data; }
|
||||
```
|
||||
|
||||
|
||||
WARNING - NOT Closures
|
||||
----------------------
|
||||
|
||||
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
|
||||
**not** closures. In particular, they do not capture their running environment. They are more like
|
||||
Rust's function pointers.
|
||||
|
||||
They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`]
|
||||
feature is turned on. This is accomplished via [automatic currying][capture].
|
||||
|
54
doc/src/language/fn-closure.md
Normal file
54
doc/src/language/fn-closure.md
Normal file
@ -0,0 +1,54 @@
|
||||
Capture External Variables via Automatic Currying
|
||||
================================================
|
||||
|
||||
Poor Man's Closures
|
||||
-------------------
|
||||
|
||||
Since [anonymous functions] de-sugar to standard function definitions, they retain all the behaviors of
|
||||
Rhai functions, including being _pure_, having no access to external variables.
|
||||
|
||||
The anonymous function syntax, however, automatically _captures_ variables that are not defined within
|
||||
the current scope, but are defined in the external scope - i.e. the scope where the anonymous function
|
||||
is created.
|
||||
|
||||
Variables that are accessible during the time the [anonymous function] is created can be captured,
|
||||
as long as they are not shadowed by local variables defined within the function's scope.
|
||||
The values captured are the values of those variables at the time of the [anonymous function]'s creation.
|
||||
|
||||
|
||||
New Parameters For Captured Variables
|
||||
------------------------------------
|
||||
|
||||
In actual implementation, this de-sugars to:
|
||||
|
||||
1. Keeping track of what variables are accessed inside the anonymous function,
|
||||
|
||||
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and in the current execution scope - where the anonymous function is created.
|
||||
|
||||
3. The variable is added to the parameters list of the anonymous function, at the front.
|
||||
|
||||
4. The current value of the variable is then [curried][currying] into the [function pointer] itself, essentially carrying that value and inserting it into future calls of the function.
|
||||
|
||||
Automatic currying can be turned off via the [`no_capture`] feature.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 40;
|
||||
|
||||
let f = |y| x + y; // current value of variable 'x' is auto-curried
|
||||
// the value 40 is curried into 'f'
|
||||
|
||||
x = 1; // 'x' can be changed but the curried value is not
|
||||
|
||||
f.call(2) == 42; // the value of 'x' is still 40
|
||||
|
||||
// The above de-sugars into this:
|
||||
fn anon$1001(x, y) { x + y } // parameter 'x' is inserted
|
||||
|
||||
let f = Fn("anon$1001").curry(x); // current value of 'x' is curried
|
||||
|
||||
f.call(2) == 42;
|
||||
```
|
@ -28,3 +28,12 @@ let curried = curry(func, 21); // function-call style also works
|
||||
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
|
||||
// only one argument is now required
|
||||
```
|
||||
|
||||
|
||||
Automatic Currying
|
||||
------------------
|
||||
|
||||
[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside
|
||||
the function's scope.
|
||||
|
||||
This is accomplished via [automatic currying].
|
||||
|
@ -21,6 +21,18 @@ When a property of an [object map] is called like a method function, and if it h
|
||||
a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be
|
||||
dispatched to the actual function with `this` binding to the [object map] itself.
|
||||
|
||||
|
||||
Use Anonymous Functions to Define Methods
|
||||
----------------------------------------
|
||||
|
||||
[Anonymous functions] defined as values for [object map] properties take on a syntactic shape
|
||||
that resembles very closely that of class methods in an OOP language.
|
||||
|
||||
Anonymous functions can also _capture_ variables from the defining environment, which is a very
|
||||
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
|
||||
can be turned off via the [`no_capture`] feature.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
[`no_object`]: {{rootUrl}}/start/features.md
|
||||
[`no_function`]: {{rootUrl}}/start/features.md
|
||||
[`no_module`]: {{rootUrl}}/start/features.md
|
||||
[`no_capture`]: {{rootUrl}}/start/features.md
|
||||
[`no_std`]: {{rootUrl}}/start/features.md
|
||||
[`no-std`]: {{rootUrl}}/start/features.md
|
||||
[`internals`]: {{rootUrl}}/start/features.md
|
||||
@ -78,6 +79,8 @@
|
||||
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||
[currying]: {{rootUrl}}/language/fn-curry.md
|
||||
[capture]: {{rootUrl}}/language/fn-closure.md
|
||||
[automatic currying]: {{rootUrl}}/language/fn-closure.md
|
||||
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
|
||||
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
||||
[anonymous function]: {{rootUrl}}/language/fn-anon.md
|
||||
|
@ -23,6 +23,7 @@ more control over what a script can (or cannot) do.
|
||||
| `no_object` | Disable support for [custom types] and [object maps]. |
|
||||
| `no_function` | Disable script-defined [functions]. |
|
||||
| `no_module` | Disable loading external [modules]. |
|
||||
| `no_capture` | Disable capturing external variables in [anonymous functions]. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
|
||||
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
|
||||
|
103
tests/call_fn.rs
103
tests/call_fn.rs
@ -83,38 +83,6 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"fn calc(x, y, z,) { (x + y) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
assert_eq!(calc_func(42, 123, 9)?, 1485);
|
||||
|
||||
let calc_func = Func::<(INT, String, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"fn calc(x, y, z) { (x + len(y)) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423);
|
||||
|
||||
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"private fn calc(x, y, z) { (x + y) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
assert!(matches!(
|
||||
*calc_func(42, 123, 9).expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc"
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -179,58 +147,33 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut module = Module::new();
|
||||
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"fn calc(x, y, z,) { (x + y) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
module.set_raw_fn(
|
||||
"call_with_arg",
|
||||
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|
||||
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
||||
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
|
||||
fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])])
|
||||
},
|
||||
);
|
||||
assert_eq!(calc_func(42, 123, 9)?, 1485);
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.load_package(module.into());
|
||||
let calc_func = Func::<(INT, String, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"fn calc(x, y, z) { (x + len(y)) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let addition = |x, y| { x + y };
|
||||
let curried = addition.curry(2);
|
||||
assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423);
|
||||
|
||||
call_with_arg(curried, 40)
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
fn test_fn_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let x = 8;
|
||||
|
||||
let res = |y, z| {
|
||||
let w = 12;
|
||||
|
||||
return (|| x + y + z + w).call();
|
||||
}.curry(15).call(2);
|
||||
|
||||
res + (|| x - 3).call()
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"private fn calc(x, y, z) { (x + y) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
assert!(matches!(
|
||||
*calc_func(42, 123, 9).expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc"
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
60
tests/closures.rs
Normal file
60
tests/closures.rs
Normal file
@ -0,0 +1,60 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT};
|
||||
use std::any::TypeId;
|
||||
|
||||
#[test]
|
||||
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut module = Module::new();
|
||||
|
||||
module.set_raw_fn(
|
||||
"call_with_arg",
|
||||
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|
||||
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
||||
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
|
||||
fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])])
|
||||
},
|
||||
);
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.load_package(module.into());
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let addition = |x, y| { x + y };
|
||||
let curried = addition.curry(2);
|
||||
|
||||
call_with_arg(curried, 40)
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let x = 8;
|
||||
|
||||
let res = |y, z| {
|
||||
let w = 12;
|
||||
|
||||
return (|| x + y + z + w).call();
|
||||
}.curry(15).call(2);
|
||||
|
||||
res + (|| x - 3).call()
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user