I'd like to start with a couple of rhetorical questions to frame my attempt at an answer.
As to why hasn't something already been done - I think that inventing new paradigms, OO or otherwise, is extremely difficult. The fact that it is hard is not a reason not to try. I have been writing programs for over 40 years and I know that I am not satisfied with where we are today. I have been writing C++ for a quarter of a century, so ought to have a good sense of what OO is not intended to be. Hopefully that makes for a solid start.
As to the vision: sometimes the original expressions of the underlying ideas, free from the baggage and assumptions, have a fresh clarity. They also contain suggestions as to the paths not taken. There may be many reasons why one path was taken over others. With today's technology, and what we know now (if we can filter out our assumptions) it may be that one of the paths not taken would now hold answers.
Since 2018 I (with others) have been using something that I think you would recognise as DCI. We have been incorporating it incrementally and shipping with it. I'm not sure what a good DCI metric would be, but we have 111 contexts containing 444 roles being used in two major sub-systems of the product. The system is concurrent and distributed (in the hundreds of network connected computers sense).
I like DCI, a lot.
With that out of the way, Cope asked: Has anyone else had the same reservations about DCI?I don't know that mine are the same reservations, but here goes...
There is a great deal that DCI gets right, but I think there is something missing that does fall into the realm of what it should seek to address.
DCI doesn't have anything to say about concurrency, but I don't want to start with this as I think there is something more fundamental.
For me, the issue is what Kay called Modularising Control
[4] (video), and, at VPRI, described as the "field" side of the { "particle" (objects) : "fields"(messages) } duality:
"Part of the key to success is that not just the “particle” side of things has to have a good model, but also the “field” side (which is often almost neglected in so-called “object-oriented” programming which is usually anything but). The “field” side has to do with messaging and events, the interstitial side of interactions (which is almost invisible because the concreteness of the objects takes too much of the center stage). In fact, the objects and the messaging system form duals of each other and have to be kept in balance for the most powerful and compact modeling." [3]To my mind this concept of Modularising Control is in some sense more fundamental than the distributed/concurrent question. Modularising Control would provide the tools to be able to better express concurrency, and control flow generally.
I don't think that Kay had the answer to how Modularising Control would be done, albeit he does reference work in this area. His sketches in the seminar video seem to me more conceptual.
So what do I mean by Modularising Control?
First, what is Control?When we look at code there will be parts that do the actual work of combining values to compute answers, and other parts whose job is firstly to get the program counter (which moves implicitly from statement to statement) to the parts of the code that will compute the answers, and secondly to gather the data to do the computing (function arguments for example). It is the guiding the program counter and gathering the data that is the Control. We are so used to the necessity of writing and reading it that it hides in plain sight. It is a huge source of non-essential complexity (see Out of the Tar Pit
[5] (pdf) for an eloquent treatment of this).
Being able to see the Control there in the code can be re-assuring, especially for novice programmers. After all, when it is all visible you can see exactly what will happen. Right? Really? Is it always easy to follow? Is the order in which it is doing things really essential?
Control code by its mere presence can obscure intent. Swathes of Control code can leave the code brittle and hard to change. Control is the opposite of a shearing layer, it is all glue. The computation part can end up like an insect trapped in amber. You can see it, indistinctly, but you can't extricate it without risking destroying it.
Code with less Control we usually describe as being more declarative. The dark side to this can be that, if we don't understand the implicit rules behind the declarative form, the code can appear "magical". Even small scale "magic" like argument dependent method dispatch (virtual functions in C++), used inappropriately, can become "hyper-galactic gotos" that could take your precious control anywhere. There is a balance to find.
So what of Modularising?We are used to modularising code artefacts in many ways: Into files, data types, classes, functions etc.
DCI helps to modularise behaviour into contexts, extracting behaviour specific state and functions from domain objects, extending objects' functionality through roles to meet particular tasks. DCI contexts could be seen in this way as "Feature Modules".
However, from a Control perspective, DCI contexts are still very much like functions. They are provided arguments (objects that will play roles) and are executed by the external system calling a method on the context. They may recursively create further contexts and execute those.
A DCI context gets to execute only as allowed by the surrounding Control, when it is called. It has modularised much of the representational aspects of state and behaviour, but is still dependent on Control.
A context cannot be lifted out of one part of a program and put into another part without the Control in that other part of the program being modified to provide its life support. It has no independence in this regard. If a Context has some part of itself that needs to operate autonomously over time, or uses some other Context that does, it cannot do so without the surrounding Control being adapted to provide control to it whenever and wherever it needs it.
Modularising Control is about finding a way of freeing objects (contexts) from their dependency on surrounding Control. From a DCI perspective we want Contexts to encapsulate Control as well as State and Behaviour. Remember, Control is not Behaviour. Behaviour is what happens as a result of Control passing through the code that expresses behaviour.
Modularising control does not necessarily imply concurrency. A system that has modular control must provide mechanisms for modules to be composed and engage with each other, as collaborating objects must. Control must be extracted and "modularised" in some way, as we already do for other things. The question is "How?".
The "something" that carries Control through and between modules (objects or "particles") might be what Kay describes as a "field".
He imagines the possibility of objects that only receive messages; messages that provide the values that the objects wants. This suggests an expectation of emergent behaviour that I think people find hard to envisage beyond systems that have evolved. Whilst this clearly does happen in biological systems, I am still at a place where I imagine software being more deliberately designed.
We already have other approaches to modularising control. Systems such as Erlang and Actors can be seen as putting control modularisation first. Each component (process/actor) having its own independent flow of control. This frees them from the tyranny of Control but comes at a cost when building certain kinds of systems. At the extreme end, the non-determinacy of the actor model, whilst very general, makes some otherwise simple things extremely hard to reason about. These independent control models are essential for physically distributed systems, if only because the hardware run them on introduces all of the same uncertainties - they cannot be wished away.
My observation is that all forms of modularisation (state, behaviour and control) are scale dependent. Different approaches can and should be taken at different scales of representation. We have to be prepared to be more Actor like on physically distributed systems, whilst at close quarters, where there are fewer failure modes, we can adopt other models that are easier to reason about.
In summaryMy reservation is that DCI doesn't have a story around modular and composable control beyond regular procedure calls at the scale of contexts.
I believe that DCI needs this because, for composition at some scales, Control concerns will overlap DCI contexts. DCI contexts shouldn't be limited to being atomic with respect to Control. A Control composition mechanism is a pre-requisite for having a good story about concurrency broadly.
A couple of illustrative examples:Consider an application that performs some long running tasks and wants to provide a web interface for users to monitor progress. Both the long running task and the web server will require Control in order to do their work. Much of the work of one will be completely independent of the other. The application will be computing results whilst the embedded web server will be listening for incoming socket connections, reading and decoding HTTP requests and so on. When the web server wants the application to render its status as HTML (or JSON or whatever) to send to a browser, the two worlds will collide. The two parts could be quite separate, live on separate threads and communicate through shared message queues using custom written Control that would coordinate access to this. But suppose we wanted them to live on one thread? Could we envisage modularising their Control requirements such that we could do this? Could DCI + something tell a story as to how this might be done?
Consider Cope's recent trygve example that simulates the "Self Organization Game"
[6] in which players, initially arranged in a circle, secretly choose two others players and then try to position themselves equidistant from the two. The example simulates the actions of each independent player and their dependencies on the positions of just two of the others. Eventually the system as a whole converges on a solution to demonstrate self-organization. The implementation is in a single DCI context running on a single thread. The code consists of the computations performed by each player, rendering code to display the state of the game and Control code that must join all of the pieces together, passing Control to each player as necessary and to the renderer as things change. There is non-trivial complexity in the Control code as it propagates player position changes, guards against infinite recursion, renders updates periodically and so on. Clearly Control is necessary in some form to run all of this on a single thread, but could some mechanism for Modularising Control allow some of these parts to be separated and then composed? If we were to create long lived contexts for each player and their selected targets, in which their movements could be planned and calculated, we would separate this from other aspects of the implementation. If we did this, how might we compose the Control required by these individual contexts with that of the others, the propagation of changes in player positions, the rendering and so on?
I would not be surprised to find adhoc Control implementations in both of these examples, indeed I might not even "see" all this code "hidden in plain sight" for what it really it is because it is ubiquitous, it is always there. We expect it and read it as somehow an integral part of the program. But it need it be?
SpoilersTo adapt DCI to work in our concurrent system I have extended it with one new idea. This seems to have worked well so far. It was only in the writing of this reply that I revisited the video of Alan Kay's old OO seminar and started to make a connection to his concept of modular control. More soon...