Skip to content

Concept Relation Programming

Giancarlo Niccolai edited this page Jul 18, 2018 · 2 revisions

(being reworked)

A Concept-Relation sub-program is a Directed Weighed Graph of Concepts connected through Relations.

Concepts are similar to nodes in traditional graph theory, with the addition of an unspecified set of value functions, which associate the Concept to a Value.

Relations are akin to weighed arcs in graph theory, with the relevant difference that weights are not values, but functions. Weights operate on set of Values extracted from the nodes with their value functions, and after some computation, they yield other Values.

With the generic name of concept value we indicate the set of all the possible values that the application of all the defined value functions to a given concept might yield.

The value of a node is either “given”, when it’s a source for the graph, or fully determined by the value of its ancestors, transferred by the weights of the relations connecting them, through the mediation of a composition function, which is concept-specific.

A C-rel sub-program consists in the evaluation of the value of the sink concepts, which are those without descendants, given the value of the source concepts. This operational step is said Graph Evaluation, and it’s considered atomic from the outside. The evaluation can be interrupted for inspection and debugging purposes, but it cannot be redirected, modified or purposefully used in other evaluations before completion.

After the computation is complete, the computed concepts can be used as sources in other sub-programs, or queried through imperative stile programming constructs.

Value types

C-rel is fundamentally a typeless paradigm, when considering the classical definition of typing. On the other hand, values in C-rel are inferred through the value function that associates values with their concept of origin.

For example, in C-rel paradigm programming model, the simple operation 1 + 1 becomes:

[Num1]   [Num2]
    \     /
   w \   / w: {@number -> @number }
      (+)
    [Result]

where Num1 and Num2 are source concepts, w is a weight function querying the number value function of the ancestor concept and transferring it to the number value function of the target, and Result is the sink concept where the final result will be stored, and has that has the + mathematical operation as its coalesce function.

Whilst theoretically possible to write a complete program in C-Rel paradigm, and while what happens behind the scene in the virtual machine is very similar to the graph evaluation described here, the language itself is not that draconian, and expressions as x = y + z, or common imperative statements as if/else will work as expected.

Under this paradigm, the typing of the values is strictly determined by the value function of the concepts. The values in the evaluation are considered typeless, as it is required that a type adequate for the upcoming evaluation is obtained from the node by the weight function, through a value function. The failure of a concept to provide a value function as the weight function requires, or receive a value as an intermediate or sink node in the graph, is considered an exception, and makes the evaluation invalid.

Aspect orientation

It is theoretically possible to perform a C-Rel graph evaluation solely leaning on Graph Theory and Lambda Calculus. However, the value functions, which are deeply integral to the theory, are somewhat akin to propertoes in OOP. On the other hand, the applicability of properties to OOP entities depends on their type, while concepts are typeless, and part of the role of the value function is that of discovering what kind of value is associated with a concept in a certain domain.

This element of the C-rel programming is better modelled by the Aspect Oriented Paradigm. By “better modelled” we mean that the AOP require less symbolic overhead with respect to any other paradigm (including functional and OOP) to express the concept of value functions.

C-rel interprets value functions as messages received by concepts, which they reply by providing the associated values. Similarly, the assignment of values to non-source concepts happens by sending them a message which request them to properly update their internal state in order to reflect the values produced by the weights.

Since aspect orientation is naturally part of C-rel, Falcon2 offers a complete implementation of the paradigm, to the point that a complete program could be written in AOP and imperative constructs only.

Concepts can be produced as single entities responding to certain messages, or as instances of classes all similarly structured, or as extensions from a base prototype. Any concept, regardless of how it was built, may be given the ability to respond to different messages, or it may drop the ability to respond to some message, or it might delegate (or required to delegate) some message handling to a different concept.

From the point of view of the “external world”, any entity, including what is normally considered a basic type (as a number or a string) is simply a concept, and can only be manipulated through message passing. As the AOP implementation is complete, messages are not limited to cover the role of value functions. OOP method-like messages can be used in this context as well.

Lambda Calculus and Functional Programming Paradigm

Another aspect of C-rel is the presence of functions acting as weights for graph arcs. The CR-Algebra on which C-rel is based leverages on lambda calculus to compute the non-source node values, and this makes functional programming, based on lambda calculus, a natural component of C-rel.

In C-Rel, any non-atomic part of the language is a function, or in other words, an expression (possibly parametric) to be evaluated. This includes “seemingly” imperative statements. For example:

if a == 1
   print(“A is 1”)
end

is sintactically and semantically equivalent to:

{if a == 1
   print(“A is 1”)
end}()

or again

expr = {if a == 1
          print(“A is 1”)
       end}
expr()

In other words, every slice of code is actually a sugar syntax for an equivalent function.

The coalesce functions

A concept receives values from the entering arcs through a coalesce function. The coalesce function is just an arbitrary function that is notified about entering arcs having provided new values. The system invokes a coalesce function for a concept under different conditions, which depends on their declaration:

  • Sequential (default): as a new value becomes available, they are immediately invoked to process it.
  • Batch: the function is invoked only when all the arc values become available.

In batch mode, if an arc presents a new value before the old one can be processed, the following options are available:

  • Queued: the new value is queued in a fifo list, and when all the parameters are available, the first item is pulled from each parameter list.
  • Overridden: the new value replaces the old one.
  • Discarded: the new value is ignored.

Also, the update might be received by different threads. The policy regarding parallel updates can be:

  • Serial: Each update (sequential or batch) is processed serially by a single agent.
  • Parallel: Each update is processed as soon as it is available, and multiple instances of the coalesce function can run in parallel.
  • Thread-specific (default): Updates are delivered in the same thread they come from. Multiple instances of the coalesce function can run in parallel, but each instance will receive data from a single thread only.

Finally, coalesce functions can declare how they see values delivered to them:

  • Arc-oriented (default): Each arc generates one parameter, which might contain different values.
  • Value-oriented: Each arc can generate multiple values, and it's the values generated by the arcs that are seen directly as parameters.

Recursion control

CR-Graph are not necessarily acyclic. This means that some processing initiated in a concept can ultimately be returned to the same concept, after being processed elsewhere.

Each concept can declare a second coalesce function that will be invoked on messages received from recursive arcs. If such function is present, the main coalesce function will deal with values from direct arcs only.

When a recursive coalesce function is present, and when both the coalesce functions are operating in batch mode, it is also possible to specify a retroaction function. It's a feature that can be useful in some corner, but relatively common cases, and would be difficult to be implemented correctly and consistently at user level; as such, it is provided natively at language level.

This function will be invoked with exactly two parameters, which are the outputs of the two coalesce functions, pairing their result by cardinality: the first result generated by the direct coalesce function is paired with the first result from the recursive one, and so on.

Of course, this functionality comes useful only when the graph is designed to perform a precise count of computations (possibly one, or one per evaluation), but that's a fairly common case in graphs that represent experimental data or conceptual databases, rather than full fledged programs. However, as there might be cases where this feature comes useful also in graphs representing programs, the following utility is provided.

The retroaction function is seen as a batch coalesce function with two parameters; as such the following options are provided:

  • Queue the excess results; or
  • Discard the excess results; or
  • Override the last pending result with the new one.

This options can be set for both the coalesce functions, setting either or both to queue, override or discard the excess results.

Transparent graphs

A transparent graph is a graph built out of simple building blocks, that are also stock resources provided at language level. The concept of a transparent graph in C-Rel is analogous to the concept of pure evaluation in a functional language: it's a graph without side effects, where the only thing taking place is a pure computation of the value of the intermediate and final nodes, given the values in the sources of the graph.

Transparent graphs should be the default choice for modelling knowledge bases and decision-oriented algorithms (which include most ML and AI algorithms), and they also have a place in common programming; as the engine can take some automatic decision supporting the computation in transparent graphs, it is advisable to create transparent graphs as large as possible, interfacing them with separate non-transparent graphs for the more logic-intense part of the computations.

Simple concepts

A simple concept just stores (more appropriately, we say it assumes) the values it receives, regardless of the structure of its coalesce and retroaction functions. When the values are updated, it notifies downstream the presence of new values, and it can be (atomically) queried for the values currently stored.

While simple concepts will not alter the values received from their external interface, they can still have side effects triggered by some configuration of their values. We distinguish between pure simple concepts, that have no such side effect logic, and impure simple concepts which require a separate processing of their state.

Simple coalesce function

A simple coalesce function passes the values to its concept (or to the retroaction function) directly, without any further processing. It can have any parameter reconciliation strategy (sequential, batch/queued, etc.) that is fit for the underlying concept.

Simple retroaction function

Analogous to simple coalesce functions, the simple retroaction function passes the value received from the coalesce functions directly to the underlying concept.

Transparent concepts

The simplest form of concept is the transparent concept, which is constituted by a pure simple concept interfaced through direct coalesce and recursion functions. Transparent concepts will be recognized by the engine as such, and special theoretical computations can take place over them, in order to compute values and relations between concepts. That values and relations can then be used in procedural logic or in other non-transparent graphs in separate, less optimized parts of the whole program.