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)
|
4. [Function Pointers](language/fn-ptr.md)
|
||||||
5. [Anonymous Functions](language/fn-anon.md)
|
5. [Anonymous Functions](language/fn-anon.md)
|
||||||
6. [Currying](language/fn-curry.md)
|
6. [Currying](language/fn-curry.md)
|
||||||
|
7. [Capturing External Variables](language/fn-closure.md)
|
||||||
16. [Print and Debug](language/print-debug.md)
|
16. [Print and Debug](language/print-debug.md)
|
||||||
17. [Modules](language/modules/index.md)
|
17. [Modules](language/modules/index.md)
|
||||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.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.
|
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
|
Disable Keywords and/or Operators
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
|
@ -49,9 +49,13 @@ fn anon_fn_1001(x) { this.data -= x; }
|
|||||||
fn anon_fn_1002() { print this.data; }
|
fn anon_fn_1002() { print this.data; }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
WARNING - NOT Closures
|
WARNING - NOT Closures
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
|
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
|
**not** closures. In particular, they do not capture their running environment. They are more like
|
||||||
Rust's function pointers.
|
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)'
|
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
|
||||||
// only one argument is now required
|
// 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
|
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.
|
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
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
[`no_object`]: {{rootUrl}}/start/features.md
|
[`no_object`]: {{rootUrl}}/start/features.md
|
||||||
[`no_function`]: {{rootUrl}}/start/features.md
|
[`no_function`]: {{rootUrl}}/start/features.md
|
||||||
[`no_module`]: {{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
|
||||||
[`no-std`]: {{rootUrl}}/start/features.md
|
[`no-std`]: {{rootUrl}}/start/features.md
|
||||||
[`internals`]: {{rootUrl}}/start/features.md
|
[`internals`]: {{rootUrl}}/start/features.md
|
||||||
@ -78,6 +79,8 @@
|
|||||||
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[currying]: {{rootUrl}}/language/fn-curry.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 namespace]: {{rootUrl}}/language/fn-namespaces.md
|
||||||
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
||||||
[anonymous function]: {{rootUrl}}/language/fn-anon.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_object` | Disable support for [custom types] and [object maps]. |
|
||||||
| `no_function` | Disable script-defined [functions]. |
|
| `no_function` | Disable script-defined [functions]. |
|
||||||
| `no_module` | Disable loading external [modules]. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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(())
|
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]
|
#[test]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -179,58 +147,33 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut module = Module::new();
|
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(
|
assert_eq!(calc_func(42, 123, 9)?, 1485);
|
||||||
"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();
|
let calc_func = Func::<(INT, String, INT), INT>::create_from_script(
|
||||||
engine.load_package(module.into());
|
Engine::new(),
|
||||||
|
"fn calc(x, y, z) { (x + len(y)) * z }",
|
||||||
|
"calc",
|
||||||
|
)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423);
|
||||||
assert_eq!(
|
|
||||||
engine.eval::<INT>(
|
|
||||||
r#"
|
|
||||||
let addition = |x, y| { x + y };
|
|
||||||
let curried = addition.curry(2);
|
|
||||||
|
|
||||||
call_with_arg(curried, 40)
|
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||||
"#
|
Engine::new(),
|
||||||
)?,
|
"private fn calc(x, y, z) { (x + y) * z }",
|
||||||
42
|
"calc",
|
||||||
);
|
)?;
|
||||||
|
|
||||||
Ok(())
|
assert!(matches!(
|
||||||
}
|
*calc_func(42, 123, 9).expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc"
|
||||||
#[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
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
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