Skip to content
Giancarlo Niccolai edited this page Sep 1, 2018 · 4 revisions

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.

Defining relations

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.

Relation concepts

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

Evaluating relations

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.

Terminal-less relations

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.

Relate function

The arrow construct

initial -> weight -> terminal

is grammar sugar for the :relate(initial, weight, terminal=nil) function.

Defining Graphs

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