You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The programmer states _how_ to calculate something, for example in terms of a function, but the actual execution of these calculations is implicitly scheduled by a runtime engine.
9
+
*[Graph model](#graph-model)
10
+
*[Propagation model](#propagation-model)
11
11
12
-
From the dependency relations between reactive values, we can construct a graph that models the dataflow.
13
-
Each reactive value is a node and directed edges denote data propagation paths.
14
12
15
-
To give an example, let `a`, `b` and `c` be arbitrary reactive values.
16
-
`x` is another reactive value that combines the former through use a binary operation `+`, which can be applied to reactive operands:
13
+
## Graph model
14
+
15
+
[Introduction to C++React]() presented several types of reactive values to model dataflow declarativly.
16
+
17
+
A pratical analogy is to think of this as a system of assembly lines that process arbitrary items.
18
+
We declare the paths on which items should move through a series of work stations.
19
+
Once we did that, the system is self-operational and the logistic details are handled automatically.
20
+
21
+
A more formal method to model the dataflow are directed acyclic graphs (DAGs), which can be constructed from the dependency relations between reactive values.
22
+
Each entity is a node and directed edges denote data propagation paths.
23
+
24
+
To give an example, let `a`, `b` and `c` be arbitrary signals.
25
+
`x` is another signal that is calculated based on the former.
26
+
Instead of invoking `MakeSignal` explicitly, the overloaded `+` operator is used to achieve the same result.
A similar example could've been given for event streams.
35
+
From a dataflow perspective, what kind of data is propagated and what exactly happens to it in each node are not relevant.
36
+
24
37
C++React does not expose the graph data structures directly to the user; instead, they are wrapped by lightweight proxies.
25
38
Such a proxy is essentially a shared pointer to the heap-allocated node.
39
+
Examples of proxy types are `Signal`, `Events`, `Observer`.
26
40
The concrete type of the node is hidden behind the proxy.
27
41
28
-
We show this scheme with an arbirary proxy type `R`:
42
+
We show this scheme for the previous example:
29
43
{% highlight C++ %}
30
-
R a = MakeR(...);
31
-
R b = MakeR(...);
32
-
R c = MakeR(...);
33
-
34
-
R x = (a + b) + c;
44
+
SignalT<S> a = MakeVar(...);
45
+
SignalT<S> b = MakeVar(...);
46
+
SignalT<S> c = MakeVar(...);
47
+
SignalT<S> x = (a + b) + c;
35
48
{% endhighlight %}
36
49
37
-
The `MakeR` function is responsible for allocating the respective node and linking it to the returned proxy.
50
+
The `MakeVar` function allocates the respective node and linking it to the returned proxy.
38
51
39
52
One observation made from the previous example is that not all nodes in the graph are bound to a proxy; the temporary sub-expression `a + b` results in a node as well.
40
53
If a new node is created, it takes shared ownership of its dependencies, because it needs them to calculate its own value. This prevents the `a + b` node from disappearing.
@@ -47,43 +60,44 @@ Assuming the handles for `a`, `b` and `c` would go out of scope but `x` remains,
47
60
Once that happens, the graph is deconstructed from the bottom up.
48
61
49
62
50
-
## Input and output nodes
63
+
###Input and output nodes
51
64
65
+
From now on, we refer to the set of inter-connected reactive values as a reactive system.
52
66
A closed, self-contained reactive system would ultimately be useless, as there's no way to get information in or out.
53
-
In other words, a reactive system needs to be able to
67
+
In other words, mechanisms are required to
54
68
55
-
* react to external events; and
69
+
* react to external input; and
56
70
* propagate side effects to the outside.
57
71
58
72
The outside refers to the larger context of the program the reactive system is part of.
59
73
60
-
To address the first requirement, we introduce designated `input nodes` at the root of the graph.
74
+
To address the first requirement, there exist designated `input nodes` at the root of the graph.
75
+
We've already seen examples of those in the form of `VarSignal` and `EventSource`.
61
76
They are the input interface of the reactive system and can be manipulated imperatively.
62
77
This allows integration of a reactive system with an imperative program.
63
78
64
-
Propagating changes to the outside world could happen at any place, since C++ does not provide any means to enforce functional purity.
65
-
However, since side effects have certain implications on thread-safety and our ability to reason about program behaviour, by convention we move them to designated `output nodes`.
79
+
Propagating changes to the outside world could happen at any place through side effects, since C++ does not provide any means to enforce functional purity.
80
+
However, since side effects have certain implications on thread-safety and our ability to reason about program behaviour, by convention they're moved them to designated `output nodes`.
66
81
By definition, these nodes don't have any successors. Analogously to input nodes, they are the output interface of the reactive system.
82
+
In [Introduction to C++React]() we've already introduced examples of output nodes in the form of observers.
67
83
68
84
69
-
## Domains
85
+
###Domains
70
86
71
87
Organizing all reactive values in a single graph would become increasingly difficult to manage.
72
88
For this reason, we allow multiple graphs in the form of domains.
73
89
Each domain is independent and groups related reactives.
74
90
The implementation uses static type tags, so the compiler prevents combination of reactives from different domains at compile time.
91
+
The downside is that the domain tag becomes part of the type, so
92
+
`Signal<S>` becomes `Signal<D,S>`, where `D` is the domain name.
93
+
To reduce the amount of typing, there exists a macro to define scoped aliases for a given
75
94
76
-
Domains can communicate with each other through their regular input and output interfaces, i.e. they can send messages from their output nodes to input nodes of other domains, including themselves.
95
+
Domains can communicate with each other by sending asynchrounous messages from special output nodes called continuations to input nodes of other domains, including themselves.
* Intra-domain dependency relations are formulated declaratively and structured as a DAG. Communication is handled implicitly and provides certain guarantees w.r.t. to ordering etc.
83
-
* Inter-domain communciation uses asychronous messaging. Messages are dispatched imperatively without any constraints.
84
99
85
-
86
-
## Cycles
100
+
### Cycles
87
101
88
102
When creating a reactive value, all its dependencies have to be passed upon initialization.
89
103
For this reason, graphs are acyclic by definition.
@@ -92,4 +106,15 @@ Creating cyclic graphs this way results in undefined behaviour.
92
106
93
107
For inter-domain communcation, cyclic dependencies between domains are allowed.
94
108
This means that two domains could bounce messages off each other infinitely.
95
-
It's up to the programmer to ensure that such loops terminate eventually.
109
+
It's up to the programmer to ensure that such loops terminate eventually.
110
+
111
+
## Propagation model
112
+
113
+
114
+
115
+
## Conclusion
116
+
117
+
In summary:
118
+
119
+
* Intra-domain dependency relations are formulated declaratively and structured as a DAG. Communication is handled implicitly and provides certain guarantees w.r.t. to ordering.
120
+
* Inter-domain communciation uses asychronous messaging. Messages are dispatched imperatively without any constraints.
Copy file name to clipboardExpand all lines: guides/Introduction.md
+43-40Lines changed: 43 additions & 40 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,50 +1,58 @@
1
1
---
2
2
layout: default
3
-
title: Reactive types
3
+
title: Introduction to C++React
4
4
groups:
5
5
- {name: Home, url: ''}
6
6
- {name: Guides , url: 'guides/'}
7
7
---
8
8
9
-
<!--
10
-
# Motivation
11
-
The [graph model guide]() explained how reactive values form a graph, with data being propagated along its edges.
12
-
It did not concretize what "value" or "data" actually means, because the dataflow perspective allows to abstract from such details.
9
+
-[Motivation](#signals)
10
+
-[Design outline](#signals)
11
+
-[Signals](#signals)
12
+
-[Event streams](#event-streams)
13
+
-[Observers](#observers)
14
+
-[Conclusion](#conclusion)
13
15
14
-
In this guide, the level of abstraction is lowered to introduce the core reactive types that make up this library.
15
-
-->
16
16
17
-
The purpose of this guide is to state the [Motivation](#motivation) behind this library and to introduce the core reactive types it provides.
18
-
Namely those are:
17
+
## Motivation
19
18
20
-
-[Signals](#signals);
21
-
-[Event streams](#event-streams);
22
-
-[Observers](#observers).
23
-
24
-
The [Conclusion](#conclusion) explains how it all fits together.
25
-
26
-
# Motivation
27
-
28
-
Reacting to events is a common task in modern applications, for instance to respond to user input.
19
+
Reacting to events is a common task in modern applications, for example to respond to user input.
29
20
Often events themselves are defined and triggered by a framework, but handled in client code.
30
-
Callback mechanisms are used to implement this. They allow clients to register and unregister functions at runtime,
31
-
and have these functions called when specific events occur.
21
+
Callback mechanisms are used to implement this.
22
+
They allow clients to register and unregister functions at runtime, and have these functions called when specific events occur.
32
23
33
-
Conceptionally, there is nothing wrong with this approach, but problems manifest when attempting to distribute complex business logic across multiple callbacks.
24
+
Conceptionally, there is nothing wrong with this approach, but problems can arise when distributing complex program logic across multiple callbacks.
34
25
The three main reasons for this are:
35
26
36
27
- (1) The control flow is "scattered"; events may be occur at arbitrary times, callbacks may be added and removed on-the-fly, etc.
37
-
- (2) Data is exchanged via shared state and side-effects.
28
+
- (2) Data is exchanged via shared state and sideeffects.
38
29
- (3) Callback execution is uncoordinated; callbacks may trigger other callbacks etc.
39
30
40
-
The combination of these points makes it increasingly difficult to reason about program behaviour and properties like correctness or algorithmic complexity.
41
-
Further, it complicates debugging and when adding concurrency to the mix, the situation gets worse.
31
+
In combination, these factors make it increasingly difficult to reason about program behaviour and properties like correctness or algorithmic complexity.
32
+
Further, debugging is difficult and when adding concurrency to the mix, the situation gets worse.
33
+
34
+
Decentralized control flow is inherent to the creation of interative applications, but issues of shared state and uncoordinated execution can be addressed.
35
+
This is what C++React - and reactive programming in general - does.
36
+
37
+
38
+
## Design outline
39
+
40
+
The issue of uncoordinated callback execution is handled by adding another layer of intelligence between triggering and actual execution.
41
+
This layer schedules callbacks which are ready to be executed, potentially using multiple threads, while guarenteeing certain safety and complexity properteries.
42
42
43
-
Decentralized control flow is inherent to the creation of interative applications, but usage of side-effects and uncoordinated execution can be addressed.
44
-
This is what this library - and reactive programming in general - does.
43
+
The aforementioned usage of shared state and side effects is employed due to a lack of alternatives to implement dataflow between callbacks.
44
+
Thus, to improve the situation, proper abstractions to model dataflow explicitly are needed.
45
45
46
+
From a high-level perspective, this dataflow model consists of entities, which can emit and/or receive data, and pure functions to "wire" them together.
47
+
Instead of using side effects, these functions pass data through arguments and return values, based on semantics of the connected entities.
48
+
There exist multiple types of entities, representing different concepts like time changing values, event occurances or actions.
46
49
47
-
# Signals
50
+
Essentially, this means that callbacks are chained and can pass data in different ways, all of which is done in a composable manner, backed by a clear semantical model.
51
+
52
+
The following sections will introduce the core abstractions in more detail.
53
+
54
+
55
+
## Signals
48
56
49
57
Signals are used to model dependency relations between mutable values.
50
58
A `SignalT<S>` instance represents a container holding a single value of type `S`, which will notify dependents when that value changes.
@@ -189,22 +197,17 @@ ObserverT obs =
189
197
{% endhighlight %}
190
198
191
199
192
-
# Conclusion
193
-
194
-
The strength of this design are as follows:
195
-
196
-
There are two key points:
197
-
198
-
- First-class objects
200
+
## Conclusion
199
201
200
-
- Avoidance of side-effects
202
+
The presented reactive types provide us with specialized tools to address problems that would otherwise be implemented in callbacks with sideeffects:
201
203
202
-
Compare this to representing events on API level, i.e. `RegisterLeftClickCallback` vs `EventsT<> LeftClick`.
204
+
- Signals, as alternative to updating and propagating state changes manually.
205
+
- Event streams, as an alternative to transfering data between event handlers explicitly, i.e. through shared message queues.
203
206
204
-
- Fine-grained ab
207
+
For cases where callbacks with side effects are not just a means to an end, but what is actually intended, observers exist as an alternative to setting up registration mechanisms by hand.
205
208
206
209
207
-
## Further reading
210
+
###Further reading
208
211
209
-
The concept of signals and event streams are established concepts from reactive programming and not original to this library.
210
-
An academic paper which describes them well and has been especially influential for the design of this library is [Deprecating the Observer Pattern](http://lamp.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf) by Maier et al.
212
+
The concepts described in this article are well-established in reactive programming and not original to this library, though semantics may slightly differ between implementations.
213
+
An academic paper which has been especially influential for the design of this library is [Deprecating the Observer Pattern](http://lamp.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf) by Maier et al.
0 commit comments