Tuesday, July 31, 2012

OFlux Modules

Code re-use is a good thing.  Developers want to have encapsulated functionality which they can easily turn into libraries so that those parts can be re-used for other programming projects.  OFlux does this using its modules system which takes its inspiration from Standard ML (or OCaml for that matter).  Standard ML extends modules a bit with an idea called functors for making modules (and their signatures -- like interfaces) generic.  OFlux does not yet support functors, but I have toyed with the idea from time to time.

Writing an OFlux module Bar involves creating the following within C++ namespace Bar:

  • a Bar::ModuleConfig structure to hold its "self" object state (each instance has its own object).  Often this object encapsulates the state of the module.  Static modules do not have a ModuleConfig structure -- so they are stateless in a way.
  • in a file called Bar.flux (and after including needed OFlux source) the module is declared using the syntax module Bar begin ... end.  All of the usual things go inside of this block (nodes, guards, flow, instances of other modules).  A module is not a recursive concept, so you can't instantiate it within itself.
  • to instantiate the module in another OFlux source location the syntax is instance Bar barinst;
  • Unless a node N (within the module) is declared with special keyword mutable, it will implicitly acquire the module instance's readwrite self guard for read access.  This provides to the C++ node function Bar::N a way to access the Bar::ModuleConfig object for that instance.  Mutable nodes acquire the self guard for write instead of read, and therefore they can change the internal instance state.
The semantics of instantiating a module is that the content of the module is notionally copied into the current scope of the program and its content (nodes, guards, etc) are just prefixed with the instance name and a "." character.  In order to allow the guards inside of a module to unify (conflating them to one guard rather than two), the syntax where guard1 = instguard1, guard2 = instguard2 is appended to the guard instantiation.  

There is no notion of inheritance between modules, since composition is done by inclusion (via instantiation).  So a developer can build modules that use other modules (e.g. we include Bar.flux in a new module which will contain an instance (or more) of Bar within itself).  Inheritance is a problematic abstraction, and the rule that inclusion should be used (indicating "has a" relationships) guided the design of this language feature.

To see an example of how an OFlux module is written, please have a look at this example in the Github repo and the top-level for that example.

No comments:

Post a Comment