5.2 KiB
this
– Simulating an Object Method
{{#include ../links.md}}
The only way for a script-defined [function] to change an external value is via `this`.
Arguments passed to script-defined [functions] are always by value because [functions] are pure.
However, [functions] can also be called in method-call style (not available under [no_object
]):
object
.
method(
parameters ...)
When a [function] is called this way, the keyword this
binds to the object in the method call and
can be changed.
fn change() { // note that the method does not need a parameter
this = 42; // 'this' binds to the object in method-call
}
let x = 500;
x.change(); // call 'change' in method-call style, 'this' binds to 'x'
x == 42; // 'x' is changed!
change(); // <- error: 'this' is unbound
Elvis Operator
The [Elvis operator][elvis] can be used to short-circuit the method call when the object itself is [()
].
object
?.
method(
parameters ...)
In the above, the method is never called if object is [()
].
Restrict the Type of this
in Function Definitions
Methods defined this way are automatically exposed to the [global namespace][function namespaces].
In many cases it may be desirable to implement methods for different [custom types] using script-defined [functions].
The Problem
Doing so is brittle and requires a lot of type checking code because there can only be one [function] definition for the same name and arity:
// Really painful way to define a method called 'do_update' on various data types
fn do_update(x) {
switch type_of(this) {
"i64" => this *= x,
"string" => this.len += x,
"bool" if this => this *= x,
"bool" => this *= 42,
"MyType" => this.update(x),
"Strange-Type#Name::with_!@#symbols" => this.update(x),
_ => throw `I don't know how to handle ${type_of(this)}`!`
}
}
The Solution
With a special syntax, it is possible to restrict a [function] to be callable only when the object
pointed to by this
is of a certain type:
fn
type name.
method(
parameters ...) {
...}
or in quotes if the type name is not a valid identifier itself:
fn
"
type name string"
.
method(
parameters ...) {
...}
Needless to say, this typed method definition style is not available under [no_object
].
The _type name_ specified in front of the [function] name must match the output of [`type_of`]
for the required type.
`int` can be used in place of the system integer type (usually `i64` or `i32`).
`float` can be used in place of the system floating-point type (usually `f64` or `f32`).
Using these make scripts more portable.
Examples
/// This 'do_update' can only be called on objects of type 'MyType' in method style
fn MyType.do_update(x, y) {
this.update(x * y);
}
/// This 'do_update' can only be called on objects of type 'Strange-Type#Name::with_!@#symbols'
/// (which can be specified via 'Engine::register_type_with_name') in method style
fn "Strange-Type#Name::with_!@#symbols".do_update(x, y) {
this.update(x * y);
}
/// Define a blanket version
fn do_update(x, y) {
this = `${this}, ${x}, ${y}`;
}
/// This 'do_update' can only be called on integers in method style
fn int.do_update(x, y) {
this += x * y
}
let obj = create_my_type(); // 'x' is 'MyType'
obj.type_of() == "MyType";
obj.do_update(42, 123); // ok!
let x = 42; // 'x' is an integer
x.type_of() == "i64";
x.do_update(42, 123); // ok!
let x = true; // 'x' is a boolean
x.type_of() == "bool";
x.do_update(42, 123); // <- this works because there is a blanket version
// Use 'is_def_fn' with three parameters to test for typed methods
is_def_fn("MyType", "do_update", 2) == true;
is_def_fn("int", "do_update", 2) == true;
Bind to this
for Module Functions
The Problem
The method-call syntax is not possible for [functions] [imported][import
] from [modules].
import "my_module" as foo;
let x = 42;
x.foo::change_value(1); // <- syntax error
The Solution
In order to call a [module] [function] as a method, it must be defined with a restriction on the
type of object pointed to by this
:
┌────────────────┐
│ my_module.rhai │
└────────────────┘
// This is a typed method function requiring 'this' to be an integer.
// Typed methods are automatically marked global when importing this module.
fn int.change_value(offset) {
// 'this' is guaranteed to be an integer
this += offset;
}
┌───────────┐
│ main.rhai │
└───────────┘
import "my_module";
let x = 42;
x.change_value(1); // ok!
x == 43;