This paper proposes a new MOP for C++, called OpenC++ Version 2. Like previous MOPS, it allows programmers to implement customized language extensions such as persistent or distributed objects, or customized compiler optimizations such as inlining of matrix arithmetic. These can be implemented as libraries ’ and then used repeatedly. Unlike previous MOPS, our proposal incurs zero runtime speed or space overhead compared to ordinary C++.
To make this possible, our MOP works by providing control over the compilation of programs rather than over the runtime environment in which they execute.
During the 1990's there were lots of cool frameworks and graphical IDE tooling for C++, somehow most of them faded away during this century, with Qt/KDE, and BeOS being a few of the survivors left.
Which in turn begat "Fresco" which then there was an attempt at a successor called "Berlin" by Graydon Hoare (of Rust fame), but it was, I believe never finished. I remember following it eagerly.
It was a dark era for Unix UI development -- before GTK & Qt, and when Motif was supposed to be "standard" but was locked behind restrictive licensing, and Sun's (actually open source) XView got no uptake (and Sun had abandoned OpenView for Motif anyways).
RogueWave Tools.h++ was one I used on Windows and, with some work, the Mac (Cooperative threads). It was a bit like the early Boost libraries. I particularly used the serialisation to send objects over the wire via TCP.
This was the paper that was the inspiration for my supervisor for my PhD topic at the end of the 1990s. That whole research direction - reflective programming languages - pretty much died on the evolutionary vine. Like AI at the time, the area was looking for use-cases.
One of the use cases for metaobject protocols was to make distributed objects look like normal objects by declaring them as such. You could write C++ classes and then annotate them (in the metadata layer) as distributed, and suddenly you had distribution transparency. Except, distribution transparency turned out not to be a good thing as it is a leaky abstraction. See the 10 fallacies of distributed systems for why this is so. EJB also killed the use case, as it was actually a solution for it. It did build on some principles from this research - enable pre- and post-method invocations to be pushed to application deployment time. EJB enabled methods to be "transactional" or "filtered" and so on. Aspect-oriented programming was a rebranding of metaobject protocols that also didn't have huge impact.
As for me, i finished my PhD in the field and moved on to other research.
There are two meanings we meant: other computers, and other programs.
In 1995 most computers had a single CPU core, running at around 100mhz. Today each core is 10-30x higher frequency (though as always you cannot compare frequency directly it is still a sign of relative speed), and almost all computers have at least 4 cores. In 1995 you were doing good to get 8 MB of RAM, now we are looking at around 8GB. One way around the inherent slowness of computers is another one on the network to run some programs.
In 1995 there were cool demonstrations where a program could plugin others for some functions. So your word processor wouldn't have a spellcheck, it would just use a third party spell check of your choice. (we sort of do that with shared libraries, but the interface is different) Some of what was done then was better than anything we have today (but see the other replies, making distributed work transparently is not easy and so if we had made it work people would probably hate how often it failed)
CORBA was another attempt of much the same thing there are dozens at the time. Of course each had slightly different goals and variations, so you can't always compare directly. Some were more to one direction or the other.
(It was not fun times. It was "hey everyone, all our problems will be solved by adopting XXX" which inevitably introduced a new set of problems which could be solved by only adopting half of XXX and all of YYY, repeat. Now that I think about it, I don't think anything has really changed, except that now the XXX has mutated into a XYZZY, we call it technical debt and then we retire to leave it for the next lot.)
CORBA is a specification to make objects interact across processes and computers. Its main drawback is the complexity, as it has to adjust to the several use cases (local and remote processes/computers) and provide interfaces to all different languages that might want to use objects, all this in a transparent way. Having CORBA specifications working reliably was a big problem to solve, and it was never 100% done, as new problems and specifications would pop up as needed. Finally, web technology solved most (but not all) of these problems by skipping programming language level objects and moving to higher level interaction through HTML/XML interfaces.
All true, but the real differentiator that enabled SOAP to kill CORBA was the appearance of NATs and firewalls. CORBA assumed objects could talk directly via IP. But NATs appeared at the end of the 1990s when IP addresses were running out, suddenly CORBA wouldn't work in corporate environments. But SOAP could be made to work with http proxies. SOAP, of course, was superceded by REST, because it was too complex and REST was perceived as much simpler.
Well, I guess that with more specifications thrown in the mix this could be covered too. The problem, as I mentioned, is the incredible complexity of the whole thing. Web technology solves these issues in simpler ways that don't involve messing up with remote objects at the programming level.
There was also Adpative Programming by Karl Lieberherr at Northeastern University (https://www2.ccs.neu.edu/research/demeter/). He had both a C++ and a Java compiler to implement what he called traversals, features that would span many classes but you could implement in one traversal file. His tools were so powerful that in one of his classes I was able to add synchronization primitives to Java as a class project.
The idea did take off big-time, just not at the level of C++ compilers and in a more modest way than envisioned. Java and C# both provided reflection interfaces to implement many of the motivating examples for meta-object protocols. Qt (user interface) and Unreal Engine have elaborate semi-automated systems for gathering metadata about C++ objects in order to manipulate them reflectively.
The architects of these systems definitely took inspiration from these meta-object protocol papers and related LISP and Smalltalk efforts. Nobody quite took it all the way, and I suspect that's because the boundaries between compile-time reflection, use site checked macro programming, and declaration site checked type level programming are quite hard to pin down in such a general system.
Unreal Engine actually applied the idea to implement distributed objects transparently. It is decent enough to power most real time multi-player gaming scenarios.
I did like Herb Sutter's relatively recent push for metaclasses though. He pointed out that people are already doing similar things in their codebases, just manually with text level preprocessors like QT uses.
If I understand this correctly, this gives users the power to add even more features to C++.
Don't get me wrong, this isn't a terrible idea in that it does have some use cases. But also, I wonder if the authors would be better off adding the MOP to a language like C which is simpler and would benefit more from having extra features.
Objects are instances of classes in CLOS. Classes, Methods, Generic Functions, ... are also instances of classes. Classes support multiple inheritance. Instances have an identity, slots and a class.
CLOS does not use methods attached to classes, which can be invoked by message sending or similar. Instead CLOS uses Generic Functions which consist of one or more Methods. Methods are dispatched over classes, using possibly multiple arguments. Generic Functions are themselves objects and they are defined outside of classes. When a Generic Function gets called, all the applicable Methods are combined to an Effective Method that then will be executed.
Well, in C you have function calls, so you can still do reification on that, turn it in to a value (ie a record that holds the parameters and the pointer of the function) and fiddle with it. Some examples:
- you can send it to another machine turning it into RPC, and you can encrypt (some of) the parameters because of this
- you can add save the program state before you execute it for checkpointing purposes
- you can log the parameters for tracing
- ...
Chiba worked with folks at PARC, and this is a year after Gregor Kiczales wrote the book, The Art of the Metaobject Protocol. Kiczales designed AspectJ, still in use in Spring today.
The three design dimensions for metaprogramming are static vs dynamic, whole-program vs incremental, and raw vs typed.
AspectJ committed to incremental (and removed whole-program features like callee-side call join points) because Eclipse's incremental compilation made enterprise programming bearable (and of course Java chose it for loading classes).
Staticly, AspectJ did reduce the pointcut shadow at compile-time (unlike (remote) method interception which), but semantically still supported type-safe dynamic checks (mainly instance-of and limited control-flow via a thread-local).
But types were the best part: unlike reflection-based proxies in Java (that formed the basis for most of the EJB/Web containers), AspectJ advice could be written to expose only the required context, and only in a type-safe manner. Inter-type declarations were guaranteed valid at compile-time (and supported default methods 20 years before Java did). And most importantly, the code with aspects was binary- and source-compatible with any code using the unmodified code.
(Of course, it also helped that Java supported debugging from extrinsic source files for JSP's, so you could step into aspects as you like.)
AspectJ static and incremental made it usable, but typed made it safe and correct and understandable.
33 comments
[ 2.9 ms ] story [ 81.7 ms ] threadThis paper proposes a new MOP for C++, called OpenC++ Version 2. Like previous MOPS, it allows programmers to implement customized language extensions such as persistent or distributed objects, or customized compiler optimizations such as inlining of matrix arithmetic. These can be implemented as libraries ’ and then used repeatedly. Unlike previous MOPS, our proposal incurs zero runtime speed or space overhead compared to ordinary C++.
To make this possible, our MOP works by providing control over the compilation of programs rather than over the runtime environment in which they execute.
https://github.com/steinarb/interviews
original paper describing it here:
http://i.stanford.edu/pub/cstr/reports/csl/tr/88/358/CSL-TR-...
Which in turn begat "Fresco" which then there was an attempt at a successor called "Berlin" by Graydon Hoare (of Rust fame), but it was, I believe never finished. I remember following it eagerly.
Everything was a hodgepodge.
Bad memories.
However things eventually moved on, when I started using GNU/Linux in production, it was either Tcl/Tk or Swing.
[1] https://www.softwarepreservation.org/projects/c_plus_plus/li...
Here’s a copy of the manual: https://bs2manuals.ts.fujitsu.com/download/manual/177
Thanks for sharing the manual.
One of the use cases for metaobject protocols was to make distributed objects look like normal objects by declaring them as such. You could write C++ classes and then annotate them (in the metadata layer) as distributed, and suddenly you had distribution transparency. Except, distribution transparency turned out not to be a good thing as it is a leaky abstraction. See the 10 fallacies of distributed systems for why this is so. EJB also killed the use case, as it was actually a solution for it. It did build on some principles from this research - enable pre- and post-method invocations to be pushed to application deployment time. EJB enabled methods to be "transactional" or "filtered" and so on. Aspect-oriented programming was a rebranding of metaobject protocols that also didn't have huge impact. As for me, i finished my PhD in the field and moved on to other research.
In 1995 most computers had a single CPU core, running at around 100mhz. Today each core is 10-30x higher frequency (though as always you cannot compare frequency directly it is still a sign of relative speed), and almost all computers have at least 4 cores. In 1995 you were doing good to get 8 MB of RAM, now we are looking at around 8GB. One way around the inherent slowness of computers is another one on the network to run some programs.
In 1995 there were cool demonstrations where a program could plugin others for some functions. So your word processor wouldn't have a spellcheck, it would just use a third party spell check of your choice. (we sort of do that with shared libraries, but the interface is different) Some of what was done then was better than anything we have today (but see the other replies, making distributed work transparently is not easy and so if we had made it work people would probably hate how often it failed)
(I know I could google all this. It’s more fun to chat about it.)
The architects of these systems definitely took inspiration from these meta-object protocol papers and related LISP and Smalltalk efforts. Nobody quite took it all the way, and I suspect that's because the boundaries between compile-time reflection, use site checked macro programming, and declaration site checked type level programming are quite hard to pin down in such a general system.
Unfortunately to this day, COM tooling remains quite clunky, despite how much WinDev pushes it since Windows Vista as main API ABI.
https://herbsutter.com/2017/07/26/metaclasses-thoughts-on-ge...
Don't get me wrong, this isn't a terrible idea in that it does have some use cases. But also, I wonder if the authors would be better off adding the MOP to a language like C which is simpler and would benefit more from having extra features.
CLOS does not use methods attached to classes, which can be invoked by message sending or similar. Instead CLOS uses Generic Functions which consist of one or more Methods. Methods are dispatched over classes, using possibly multiple arguments. Generic Functions are themselves objects and they are defined outside of classes. When a Generic Function gets called, all the applicable Methods are combined to an Effective Method that then will be executed.
Just because some language does not have classes doesn't mean you cannot implement a functional substitute.
Chiba worked with folks at PARC, and this is a year after Gregor Kiczales wrote the book, The Art of the Metaobject Protocol. Kiczales designed AspectJ, still in use in Spring today.
The three design dimensions for metaprogramming are static vs dynamic, whole-program vs incremental, and raw vs typed.
AspectJ committed to incremental (and removed whole-program features like callee-side call join points) because Eclipse's incremental compilation made enterprise programming bearable (and of course Java chose it for loading classes).
Staticly, AspectJ did reduce the pointcut shadow at compile-time (unlike (remote) method interception which), but semantically still supported type-safe dynamic checks (mainly instance-of and limited control-flow via a thread-local).
But types were the best part: unlike reflection-based proxies in Java (that formed the basis for most of the EJB/Web containers), AspectJ advice could be written to expose only the required context, and only in a type-safe manner. Inter-type declarations were guaranteed valid at compile-time (and supported default methods 20 years before Java did). And most importantly, the code with aspects was binary- and source-compatible with any code using the unmodified code.
(Of course, it also helped that Java supported debugging from extrinsic source files for JSP's, so you could step into aspects as you like.)
AspectJ static and incremental made it usable, but typed made it safe and correct and understandable.
(And btw, Kiczales dropped out of MIT.)
[1] https://www.circle-lang.org/