Reactive Actors (RA) should be structured like ordinary classes. But unlike of classes, it should be distinct internal inputs/outputs, external inputs/outputs and internal 'shell' state. In case of simple RA, it should be external i/o and internal state. For simplest RA, internal state doesn't exist, for ex. actor Sum
could always recalculate its outputs and don't store state inside - calculation result is the last one, if its inputs would be disconnected or emptied, it will empty its output.
Reactive Actors have their inputs in their public input properties. Their conception is like 'slots' in Qt.
Reactive Actors have their outputs in their public output properties. Their conception is like 'Observable' in pub/sub systems.
Inputs and outputs always have value semantics. Output always exist as a value container. It could be empty. Inputs are distinct from outputs by a way in which they could be connected or not. Actor never should 'mind' about its outputs connecting state - it should not be exist.
So, inputs can be connected or not. Ever if connected, they could have empty values - because their connected outputs of another actors are exhibit empty values on their outputs.
Reactive Actors processing happened when an input is first connected to an output of another actor or a connected output value has changed.
RA recalculate function activated each time when its input changed. There is no internal 'processing' possible by something time-clock generators. Ofcourse, RA recalc functions could be written so they recalculate its outputs in long calculation process and provide time-like semantics, but it should be avoided writing such while(1)
-like code.
A Reactive Actors engine should not store property values in actors inputs and shouldn't queue it too. If actors should cache previous state, they should do this explicitly in their recalculate function.
Outputs should be presented as inherent properties of the actor - when an actor changes a property, it should be replaced by new value and change notification should be distributed to all subscribers connected externally.
Outputs should be connected with inputs by information-controlled process in the containing actors. It is exist internal exhibited properties which are allowed to internal actors to observe. Connection control process should be described in declarative terms instead of functions/methods calls.
It should exist a standard mechanism (observable properties) which directs internal actors' connections. So, containing actor should be capable to exhibit such controlling values to its internal environment - which is actor itself with a role to connect another actors.
So, any container actor should contain actor 'internal-environment'. Without thinking about creation semantics let's suppose that two actors already exist inside actor-container. To exist inside, the actors should be filled in 'internal environment', it should know about their outputs and inputs. So, container actor can query int. env. actor which actors it wants to connect and specify which outputs and which inputs.
To be implemented in available code, for ex. in Javascript, container semantics should be explicit.
Classes are exist in ideal environment, without time and space. They are ideal constructs describing how to create its instances.
Each programmer should write classes and structure his/her program in OOP manner. We don't write objects directly contained inside another objects. We write distinct classes, A and B, then say to class A: 'you should have your internal property of type B which would be your inherent part'. Our code describing such containee object is not in A. It is in B.
But controlling code is in A. It sends state changes to B, it receives state changes from B.
Actor-like objects should be implemented as pure functions with inputs and outputs. Their purity is not as in FP. The actors are pure in sence of time: its outputs solely depend on its previous inputs.
For each current moment in time, actors calculate their output values and present them as observable-like properties visible from outside. The properties are connected with inputs of another actors by connectors, which translate current values from outputs to inputs. After receiving a new value on any input, actors should re-calculate their state (external observable) and present it in reactive manner.
Any function/method call semantics should be replaced by a property exhibition - external or internal (of actors-containers).
Values which circulate on connecting channels are stored on outputs of actors. Each time when actors should have its own copy of external properties, they should explicitely make their own copies. So, channels 'transmit' just references to values.
Word 'transmit' is quoted because of there is no transmission in ordinary sense. Channels transmit notifications about property changes - not ever pointers to their new values. So, recalculating function could be used for 'lazy'-like evaluation of its outputs.
When input value is really needed, it could be acquired by delta-CRDT transmission process. Deltas are always calculated when an actor changes its outputs (actually, values are stored in delta-CRDT logs, so, we can get any historical value at any time when need it, although actor could compress the value to its latest state).
So, any change of observable actor state (its outputs) should lead to point notification that interesting part of the state is changed. Subscribing actor (subscriber) should immediately observe its inputs change events and run recalculation. It is not clear if actors ever should have its non-observable internal state persisted between its reactive recalculations.
By definition and design, containers actors are implementors of all internal connections of all their internal actors. By design, all contained actors are part of the structure of containers. Live cell-like actors which have their shell are another actors which are part of the container actors. There are internal inputs and outputs and external i/o. So, difference between them is in container actor's explicit public interface. Cell-like container actors are not necessary!
Container actors just could describe their internal actors connections explicitly in their constructor's definition and specify which inputs and outputs of its internal actors are external and which are part of internal state interactions.
A: to describe all connections between internal actors; B: to describe which inputs and outputs to be external.
The most complex thinkable part of the thing is a question, how to write reactive code. The function can be composited by small pure functions, which gets their inputs and calculate their outputs. The most simple implementation can be activated each time when ever a single input changes. Such simple reactive function uses all values of it inputs like all of them are supplied only just now.
To make reactive calculations possible, we should introduce empty
and not connected
semantics for all used variables.
Calculation process in any expression should support each of three possible states. Most nearest concept is monad Maybe
in Haskell and other functional languages. We should write our code in such style which uses this Maybe
semantics.
A short and efficient intro into Maybe
monad: http://sean.voisen.org/blog/2013/10/intro-monads-maybe/
Most usable pair to monad Maybe`` for Reactive Functional Actors: monad
Either```: http://folktalejs.org/data.either/
A short description from a comment on http://kawagner.blogspot.ru/2007/02/why-monads-are-evil.html :
Monads merely provide a structuring mechanism that also allows you to denote imperative constructs. One way of looking at them is in terms of overloading the semicolon that separates instructions in a sequence.
Take that last word into account: sequence. Monads only allow a linear sequence of monadic values to be bound together into one monadic value. Therefore monads most definitely do not allow you to generate any semantics you want.
Further on you seem to want to reclaim the functionality in the functional language, using the world as input to your program. However, any output logically has to become part of the world, therefore the program should be of type world -> world. What is more, the world cannot logically be duplicated by the program. And there exactly is the issue: with referential transparency there is no problem in duplicating any value, as the result of a program does not get changed by it. Therefore, functional programs are normally free to do so. With the world, they should not. And exactly that is what monads allow you to express: you can not diverge from the sequence of functions operating on the world. Therefore you cannot duplicate the world.
There could be relation of many to many. Each actor represents a value. The value is current state of the object. Subscriptions are assigned by external connections.
When an object changes its state it fires an event via all connections.
Event slots are simple methods like setValue(obj)
, where obj
- is just instance reference of changed object. In fact, its value could be ever not calculated at all (or not required by the setXxx
method.
The method's implementation access obj
itself to get its value. We should distinct changed
events and outputs (re)calculations.
By design, setXxxx()
methods only access values they know. In ideal, we should pass a binded to obj
getValue()
function instead of direct obj
- each time when an object changes.
It should be possible to connect multiples observables to an input.
A most complex thing in such solution is some kind of discipline in process of observable state changes.
It is often needed to know not only new object's state, but reason (concrete events) which leads to the state change. Therefore, to not create a lot of distinct concepts, we could represent our state together with its changes sequence.
Subscription should be full (and not partial) - to the whole object state changes (and the state itself).
Also, it could be very convenient and smart to specify state changes by some means of a command/events language, because dumb diffs could be calculated by consumers if need, but important reasons of change are not - and simply could be exhibited be those state change events which moves to calls of setInputNamedLikeBlaBlaBla(getter)
: the reason could be simple change event which is emitted by changed object.
The main important difference from systems like this is that all of the events comes as an input (in all sorts of events/signals subscriptions I know there is 1:1 mapping). But in our system, an object could generate a lot of distinct events all of which should come to a single downstream subscription channel without opt-in. And the object itself is accessible (digestible) by getter callback function which is supplied into the slot together with the change
event. It is subscriber's busy deal to decide use or not the event's semantics, call or not the supplied getter function, call or not setImmediate()
or nextTick()
to make its own state changes directly at the call of its setXxx()
method or defer its state change and generating "I'm changed!"
events to some later stage - perhaps, at async time ever.
So, value (current state) and events (last state transition from previous state to the current one) should be distincted by a developer. Semantics of value should be differentiated for each use case - sometimes it should be needed to store complex state history as a state itself, sometimes it should be buffers at next layers of processing.
Loops are welcomed! Complex behavior is achievable by complex connections of inputs and outputs.
Event emitters, async event sources all are welcomed - an object could change its state at each moment in time it wants. At the moment, our notification framework should dispatch the event to all of connected setXxx() methods by means of direct calls in a loop of all subscribers. Therefore, each actor must be implemented in a manner that each its method could be called asynchronously at every surprising moment. So, if it makes a change on its internal state, it should queue such change explicitly: internal events are very important part of our implementation. But simple actors could change their state directly by such call and emit their changed events cascadely. I repeat, setImmediate() and nextTick() call stack isolation is optional!
State change event should be fired afer the state change and it should describe at application level the state change (or the reason of it's change).
I repeat, regardless of events types count emitted by an object, it should be only one slot (subscriber, listener) for all of them - each suscribee should be called at any state change of an object. In such way we could implement very pure functional/reactive mapping to actors/signals model and preserve lot of SoC and purity, so, maintenance and readability.
But single slot could be (surprisingly!) connected to multiple signals simultaneously in our way - the feature unthinkable for such sort of systems and connections between reactive functor-like/actor-like objects.
Multiple inputs could be implemented by common CRDT source which was an initializer for multiple objects - each of them could change independently later, but both versions could be simply merged at setXxx()
side - in such way that multiple sources update single CRDT value. Therefore, multiple inbound connections should be supported only for CRDT types which were initialized form the same source. By such means, we observe a strange duality between inputs and outputs as resulting states.
So, ever out setXxx()
methods could be not setters but transitioners which describe some transition form an old (maybe unknown and unobservable) state to a new one. So, semantics of such transitioners should be defined correspondingly.
Therefore, a reactive actor function is some function which operated over its inputs changes and not on input values itself.
We found that most simple change description is last state state itself and short last event describing the change - together.
In principle, all inputs could store its previous state values to easily observe detailed state change in their own semantics. But it should be work of our framework to dispatch to those transitionSomeInputStateChangeFromTo methods correct pre-calculated references which depend on subscriber's subscription state. So, 'introduce' or 'it is exist a value' events should be generated automatically by change distribution function when a subscriber just connects to an observable value.
So, to framework to work, actors should implement some special methods for framework: _getValue() should return object's latest state - and it will be called each time when an actor emits a state change event.
Never more complex connectors which connect signals and slots! Just connect an actor's state to a transitioning input method of another actor (and specify that actor too). Just at time of the connection it will be first transitioning input method's call. Instead of 4 arguments for connecting your signals and slots it would be 3.
https://github.com/folktale/data.either https://github.com/folktale/data.maybe
Work in progress. Comment here on English, please.