Reading List

The Selfish Gene
The Psychopath Test: A Journey Through the Madness Industry
Bad Science
The Feynman Lectures on Physics
The Theory of Everything: The Origin and Fate of the Universe


ifknot's favorite books »

Wednesday 3 September 2014

C++11 Variadic template factory member function fun! Building a FBP directed graph class.

Better by variadic design?


TL;DR The libfbp library needs a directed graph container for the ensemble of communicating components that makes FBP programming as easy and error free as possible. Achieving these goals by giving the directed graph class the responsibilities for creation, storage, lifetime and execution of its components and connections poses the challenge of how to implement generic factory methods that allow the creation of a zoo of unknowable components and connections?  C++11/14 variadic template metaprogramming to the rescue!


With the mathematical semantics in hand for describing any FBP program as a graph theoretic ensemble of CSP components communicating via named connections the libfbp library needs an implementation of the executable FBP directed graph.

The constructing, containing, executing and disposing of components and connections presents a significant and unnecessary cognitive load to the user of the libfbp library. Keeping the wetware working memory of the FBP programmer free of such, effectively house-keeping, duties is expected to make the task of FBP programming using libfbp both easier and less error prone.

Therefore, the directed graph class should not simply be a storage container for the components and connections but also hide the creation, lifetime management and execution from the would-be FBP programmer.

It follows naturally then that a directed graph object is needed, not in the general purpose sense but rather one specific to the creation, storage, life time management and execution of FBP components and connections. Decomposing these design goals into their implementation description:

Component(s)

  • Creation: Given the potentially infinite, and hence unknowable at compile time, variety of concrete components, it follows that creation will necessitate a generic factory method pattern; or in C++ terms a template factory member function. However, this template factory member function will need to be able to handle an unknowable number of unknowable types required by the equally unknowable component type for creation! Further, because libfbp components are not reentrant then each component will have to be unique and uniquely addressable, hence, possess a unique identifier - the std::string name of the component.
  • Storage: Confirmable unique addressable storage is best served by the std::map structure.
  • Life time: The logical lifetime of the component is dictated by its utility but the knotty problem of how to handle its physical storage lifetime remains. The initial appeal of permitting apoptosis by self-deletion once its utility has expired would present storage problems to fbp::graph. In this way, despite the resource freeing appeal of self-deletion, the decision is dictated by the need to maintain absolute decoupling of the component from its environment and avoid dangling pointers in the graph object. Therefore, a map of component name keys to std::unique_ptr item component* will ensure the RAII model of component lifetime and ensure each component is unique and uniquely addressable‑as required.
    The desired lifetime management and uniqueness of components can be assured and memory leaks avoided by preventing the user from free reign creation. User component creation can be achieved by passing the desired component type as the template parameter to the factory member function along with a unique std::string identifier. Then, because libfbp components, as per their CSP theoretical basis, are opaque to the environment, the user has no need to access nor manipulate the component directly and components can stay safely under the auspices of the graph object. The user needs only the ability to specify how the components are connected, which can be achieved with the components' unique identifiers.
  • Execution: Since it is not knowable at design time the nature of the underlying PXM only the contract that it must fulfil will be to execute the operator()() of a component and behave accordingly to its return values (as previously described here) and to comply with a very simple interface that permits the black bag addition of components that have the guarantee from the underlying PXM of being given opportunity to execute at some point.

Connection(s)

  • Creation: Again, given the potentially infinite, and hence unknowable at compile time, variety of concrete connection classes then a template factory member function that takes a connection type and the names of the components to be connected and an, optional, connection name. The connection name is optional because, although in libfbp each connection can only be shared between 2 ports of 1 or 2 components, and no more, it is the case that each connection must be unique, but, since the connection itself is a passive object invisible in its own right to the PXM it does not need to be uniquely addressable. Indeed, the name, at least for one default connection, is optional. However, given that the connection itself must be unique then taking its construction out of the hands of the user will guarantee this part of the contract is honoured.
  •  Storage: Given that, in terms of the connection, fbp::graph is looking to store a unique element where the value itself is the unique key then the std::set presents itself as the obvious choice of data structure. However, because the connection uniqueness guarantee is assured by its creation being only via the graph connection factory member function then the std::vector will serve ideally for connection storage.
  •  Life time: As for the component so for the connection, whose logical lifetime is dictated by its programmatic utility but its physical lifetime can be subsumed to its containing vector of std::unique pointer. However, as in life, there may be unintended consequences from any software engineering design decision and it may be the case that in an FBP solution that requires a very large turnover of nodes and connections that some form of garbage collection would need to be implemented to recoup storage from dead components and connections?
  •  Execution: As has already been expressed the PXM is oblivious of the independent existence of connections themselves and the execution of a connection's member functions takes place via their connected components.

fbp::dgraph

(Code snippets now with added explanation!)

Static versus runtime graph construction

It is clear from the design description so far that the FBP program graph itself is constructed at compile time from the programatic or, potentially, graph language description presented. However, it remains (very) appealing to enable libfbp to extend the FBP program graph or, for that matter, construct subgraphs at runtime.

Such functionality is particularly desirable to enable libfbp to address those classes of problems whose size can not be known at compile time. Such functionality does not necessitate breaking the 4th wall of component isolation and can be achieved by extending the language with a global function(s) that permits the spawning and connection of new components. But, as has already been alluded to in the lifetime discussions for component and connection, the storage challenges imposed by implementing runtime graph construction and/or extension remain to be addressed.

What next?

Onto the canonical FBP program and no, it's not "Hello World!"

No comments:

Post a Comment