I’m feeling SADR today than I was yesterday.
I spent this morning designing a generic context object atop Haskell’s Data.Typeable. The idea is to thread the context object through construction of a Sirea behavior. The context object makes it feasible to:
- Automatically create a thread for each logical partition (by type) without specializing Sirea’s behavior type. (Prior design had me specializing a `
bcross` behavior, which is something I always hate doing.)
- Enable these threads to perform heavy-duty (but wait-free or at least interruptible) tasks in addition to managing RDP computations. For example, a GL thread could be responsible for displaying at 60fps in addition to RDP communication.
- Build simple communications mechanisms between partition threads and the RDP behavior, i.e. so RDP can supply what to display and the GL thread can be completely generic (via typeclass). The threads should communicate only via RDP, not directly with each other. Sirea would ensure snapshot consistency and a `frozen world` view between steps.
These are all features I want, but they aren’t all of the features I want.
The context object design has several weaknesses:
- Behaviors are difficult to compose. They can interfere via context even if they have no explicitly shared dependencies.
- If I use multiple contexts for independence, I cannot effectively share dynamic behaviors between them. The meaning of the behavior would change based on context.
- Modularity is hindered; behaviors are coupled to specific FFI modules, e.g. for OpenGL. It is difficult to transparently replace the back-end of such programs.
- Testing and debugging are hindered. It is difficult to use mock objects for testing, or to audit resource use to see what a program is doing.
- Modeling distributed programs is hindered. We need multiple instances of similar behaviors with slightly different contexts.
These problems aren’t news to me. RDP was designed with object capability model discipline, security, and design patterns in mind to solve these problems and more. I’ve even spent some time working on suitable module system designs.
But my attitude, when approaching Sirea, has been to favor the Haskell way of doing things. And Haskell’s approach to resource acquisition is about the same as C’s – ambient authority, global state, tight coupling to FFI modules, and self discipline to brave dependency hell.
I wonder if I can do better without sacrificing the dubious convenience of the familiar Haskell approach.
The most important property to me is that the meaning of a behavior depends on the context in which it is defined, not the context in which it is executed. I.e. lexical scope instead of dynamic. I could then define or acquire sub-contexts whenever I desire to control dependencies between subprograms – even create entirely `fresh` contexts to model complete isolation (up to IO). I could rely on some design patterns and self-discipline to reduce FFI coupling – i.e. embed interface objects in the context.
This separation of context means that I must supply context to define the behavior, rather than supply it after the behavior is built. Basically, it’s staged programming.
The convenience challenges then are:
- Developers should not be explicitly plumbing `cx` parameters through their code.
- It must be easy to refine contexts for different sub-programs.
- It should be feasible to shift gradually and semi-transparently from using hard-coded FFI to using context-based resources.
Point two could probably be met by annotating context options with plenty of metadata, i.e. so it is easier to create contexts by use of filtering and composition. Point three should fall naturally out of using a `generic` context type, i.e. I can transparently replace the code that uses GLUT directly with code that first checks the context for a GLUT interface-object and otherwise falls back on creating one with IO.
The first point is difficult. One possibility is to use GHC’s implicit parameters extension. But supplying the context while building the behavior doesn’t really appeal to me. I could try building behaviors in a State monad with context, but I think that would be even less convenient. I’d like greater separation of concerns and syntax. I’m thinking to model a static arrow transformer or similar for Sirea’s RDP model so I can supply context wherever it is needed, and manipulate sub-contexts where necessary.
If I can get this right, I will be able to compose behaviors safely and securely while still implicitly (and typefully) `declaring` the creation of threads and resources and GLUT windows to be controlled by the Sirea behavior – which is a convenience I’m not prepared to abandon.
Addendum: I’d also like to have pure dependency injection, but I have nary a clue where to start there. For now I’ll stick with the hackish mechanisms I developed for plain old CX.