How to use expressions¶
Observable expressions are a simple way of composing observable values and constants.
Simple expressions¶
You can use operators, like +
and *
, to combine observable values into
arbitrary expressions. These expressions are converted to values by passing them
to the observe() functions.
Note
If the observable value’s contained type does not support an operator, neither will the observable value.
Here’s a small example:
#include <cmath>
#include <observable/observable.hpp>
int main()
{
auto radius = observable::value<double> { 5 };
auto circumference = observe(2 * M_PI * radius);
assert(fabs(circumference.get() - 2 * M_PI * 5) < 0.001);
radius = 7;
assert(fabs(circumference.get() - 2 * M_PI * 7) < 0.001);
}
The radius
variable is an observable value. Because of that, it can be used
in an expression, which, when passed to observe, produces another value:
circumference
.
The circumference
value will be updated each time radius
gets changed.
Using updaters¶
In the previous example, the update is automatic and immediate, but you can control when the expression is recomputed by passing an updater to observe().
Here’s how using an updater works:
#include <cmath>
#include <observable/observable.hpp>
int main()
{
auto radius = observable::value<double> { 5 };
auto updater = observable::updater { };
auto circumference = observe(updater, 2 * M_PI * radius);
assert(fabs(circumference.get() - 2 * M_PI * 5) < 0.001);
radius = 7;
assert(fabs(circumference.get() - 2 * M_PI * 5) < 0.001);
updater.update_all();
assert(fabs(circumference.get() - 2 * M_PI * 7) < 0.001);
}
When you call update_all()
, all values returned by observe()
calls,
which got passed the updater instance, will be updated with the new result
of the expression.
By using updaters, you can have complete control over when expressions are evaluated.
You can use multiple updaters at the same time.
Another benefit of using updaters is that your expression will not be evaluated
multiple times, because you can delay the call to update_all()
until
you know that all values that will be changed, have changed.
Expression filters¶
An expression filter is a callable object that can take one or more expression nodes as parameters and returns an expression node [1]. Basically, it’s a function that can be used inside expressions.
[1] | The low-level components of an expression are called expression nodes. |
Predefined filters¶
There are a number of predefined filters, that you can check out in the reference.
Like with operators, you can take advantage of ADL and just use the filter’s unqualified name.
#include <cmath>
#include <observable/observable.hpp>
int main()
{
auto radius = observable::value<double> { 5 };
auto area = observe(M_PI * pow(radius, 2));
auto is_large = observe(select(area > 100, true, false));
assert(fabs(area.get() - M_PI * std::pow(5, 2)) < 0.001);
assert(is_large.get() == false);
radius = 70;
assert(fabs(area.get() - M_PI * std::pow(70, 2)) < 0.001);
assert(is_large.get() == true);
}
User defined filters¶
You can write your own expression filters.
It’s pretty easy as you won’t need to handle the expression nodes directly; just write a normal function taking the right values and use the OBSERVABLE_ADAPT_FILTER macro.
The predefined filters are created with the same macro.
Let’s take a look at an example:
#include <cmath>
#include <observable/observable.hpp>
double square_(double val) { return std::pow(val, 2); }
OBSERVABLE_ADAPT_FILTER(square, square_)
int main()
{
auto radius = observable::value<double> { 5 };
auto area = observe(M_PI * square(radius));
assert(fabs(area.get() - M_PI * std::pow(5, 2)) < 0.001);
radius = 70;
assert(fabs(area.get() - M_PI * std::pow(70, 2)) < 0.001);
}
The function that you provide to the macro will be called each time the expression is evaluated, so keep it fast.
The filter will be declared in the same namespace where the macro is used.
Conclusion¶
Instead of using subscribe and callbacks, expressions are an easy way of constructing and updating values.
Check out the expression reference for more details.