-
Notifications
You must be signed in to change notification settings - Fork 5
Graphs
While concepts are the main data structure in Falcon2, Graphs are the main procedural structure. For procedural structure, we mean the way in which the program logic flows.
Structurally, the graph is a concept holding set of relations. Relations can be seen as functions getting values from upstream concepts, and presenting transformed values to downstream concepts
The procedural flow is automatically parallelized, and each computation is performed when necessary, in order to transfer the values to all the sink concepts.
After a graph is evaluated, all the sinks (and intermediate nodes) receive the updated values.
A relation is defined with the arrow construct:
Source -> weight -> Sink
where Source
and Sink
are concepts, and the weight
is a function (or a parametric expression).
The function parameters are the values pulled from the Source with a value request message, while
it's evaluation result goes into the coalesce function of the sink.
For example:
A -> {(v1, v2) v1 + v2 } -> B
is somewhat equivalent to:
B.coalesce(A.v1 + A.v2)
Actually, something more complex goes on in this process, with parallelism, error management, caching copying and other automatisms being managed by the virtual machine.
The arrow construct generates a relation concept that can be used to manage the relation.
R = A -> f -> B
print(R.initial) # A
print(R.weight) # f
print(R.terminal) # B
R.detach() # The relation is canceled
Once created, the relation is immutable; however, it's possible to alter the graph where the relation takes place by detaching the relation and creating a new one.
The sink and source concept themselves aren't oblivious about their connections in the graph. The relation concept keeps also traks of other relations having their initial and terminal concepts as sources or sinks.
- Relation.sources returns a pair of sets of relations having the initial and terminal nodes as their terminal
- Relation.sinks returns a pair of sets of relations having the initial and terminal nodes as their initial
Generally, relations are evaluated in a graph context (by evaluating the graph in which they are defined). However it is possible to evaluate the relation only.
R = 4-> {(asNumber) asNumber*2} -> Sink
R()
print(Sink) # 8
For all intent and purpose, evaluating a relation corresponds to creating an anonymous graph containing the relation only, and evaluating that graph.
It is possible to define a relation comprising the initial concept and a weight function only, in order to perform some computation on the values extracted during the computation. For example:
R = C -> {(value) print(v1+v2)}
Evaluating R()
won't modify C
, but will print the sum of the values v1
and v2
.
The arrow construct
initial -> weight -> terminal
is grammar sugar for the :relate(initial, weight, terminal=nil)
function.
Graphs are structurally just concepts in which relations are defined. Using the arrow construct (or the :relate
function) within the definition of concept is enough to define a graph.
For example:
concept Incrementor {
function derive {( soruce, sink )
source -> {(asInt) asInt + 1 } -> sink
}
}
target = :Number()
inc = :new(Incrementor, 5, target)
:flow(graph)
print(target) # 6
Here, the derive
method is invoked by :new
, which creates a simple graph in Incrementor
with one relation only. The :flow
function evaluates the graph, which basically invokes the weight on source, taking its asInt
value, and sending the value incremented by one to target
.
By default, graph concepts are not provided with any special member, and their relation network is entirely manipulated by functions in the generic namespace as :flow
. This means that it's not normally possible to delegate graph related features. However, the :graph
base concept, available in the common namespace, provides methods analogue to the graph functions, and can be used as a prototype if so desired.
For example, the previous program is equivalent to:
concept Incrementor {
:proto(:graph)
function derive {( soruce, sink )
source -> {(asInt) asInt + 1 } -> sink
}
}
target = :Number()
inc = :new(Incrementor, 5, target)
graph.flow() # defined as `{(...) :passvp(:flow, self)}`
print(target) # 6
more to do...
- Defining values
- Named results {() ... => valueName}
- Coalesce functions
- Retroaction functions
- Mutuality functions
- Parametric flow evaluation
- Correlation