[Oberon] Class Methods Vs. Procedure variables in Records
andreas_pirklbauer at yahoo.com
Mon Feb 27 15:05:12 CET 2017
Great additional remarks, Josef.
With respect to the „Draw“ editor, developed by Prof. Wirth at ETH, and which is available at www.inf.ethz.ch/personal/wirth/ProjectOberon/Sources/Draw.Mod, let me add a few additional remarks:
This editor effectively combines the two main styles of OOP usually put forward: (1) installing procedures (called „methods“ in OOP terminology) in procedure-typed record fields in extensible records and (2) sending „messages“ in the form of extensible records passed as VAR parameters to installed “handler” procedures, which then procedes by executing through a cascaded IF statement to distinguish between the various messages types (which are typically extensions of some base type).
The refined technique used in the Draw editor is to include only a *single* field (called „do“) in *each* record instead of multiple fields, such as draw, move, copy, etc. This “do” field is simply a pointer to a method record containing the actual methods declared for the base type, which is generated when a new object type is created, typically in the initialization sequence of the concerned module.
This is nicely illustrated in module Graphics.Mod, which is used by the Draw editor, and which can be found at www.inf.ethz.ch/personal/wirth/ProjectOberon/Sources/Graphics.Mod
There, we find the following type definitions:
ObjectDesc* = RECORD
x*, y*, w*, h*: INTEGER;
do*: Method; (*the pointer to the method table*)
MethodDesc* = RECORD (*the method table itself*)
module*, allocator*: Name;
copy*: PROCEDURE (from, to: Object);
draw*, change*: PROCEDURE (obj: Object; VAR msg: Msg);
Then, in the module initialization code of module Graphics.Mod, the “method table” needs to be set up once, which is simply done as follows:
NEW(LineMethod); LineMethod.new := NewLine;
LineMethod.copy := CopyLine;
LineMethod.selectable := LineSelectable;
LineMethod.change := ChangeLine;
LineMethod.read := ReadLine;
LineMethod.write := WriteLine;
And, finally, when a new object is created, a pointer to this one record (LineMethod in the above example) is assigned to the “do” field of the new object descriptor:
VAR line: Line;
BEGIN NEW(line); new := line; line.do := LineMethod
With this simple setup, a method call simply has the form obj.do.write(obj, R) .
So here you have your „method table“ - it corresponds exactly to the method table used in Object Oberon or Oberon-2... but is programmed „by hand” so to speak.
We note that the approach used in the Draw editor eliminates the need for (multiple) individual method fields in each object record as well as the cascaded IF statement for discriminating among the message types. And yet it allows further addition of new methods for later extensions without the need to change the object's declaration.
A note regarding performance: Because of the fact that the offsets to the methods are now known *at compile time* (by virtue of their known position within the method record), the compiler can generate efficient code for the „method call“, namely as follows: first you dereference obj.do to get to the method table (indirection #1), and then you dereference obj.do.write to get the actual method (indirection #2). Easy.
So this really works *exactly* like the method table in Object Oberon or Oberon-2, except that the underlying mechanism is now made explicit and fully visible in the source code (vs. hidden as in Oberon-2 and Object Oberon). This of course not only means rather efficient upcalls, but also much greater flexibility. And it shows that “classes” (introduced by Object Oberon) and “type-bound procedures” (introduced by Oberon-2 in its attempt to eliminate the class concept) can be viewed as a kind of “syntactic sugar” – and indeed, everything can be programmed “by hand” in Oberon-1, as shown in the Draw editor.
So this way of using OOP effectively muted the „performance argument“, which was all about more efficient upcalls in the “class-centered” view, and which was one of the three main arguments put forward by the proponents of the “class-centered view” (Object Oberon and Oberon-2 camp around Prof. Mössenböck), as outlined in my post from January 3. The other two reasons were a somewhat more convenient notation (you write v.handle(M) instead of v.handle(v, M) and increased security (by guaranteeing that all objects indeed have their handlers installed and also that they are the same for all objects).
But again, the main argument *in favor* of the “instance-centered view” (represented by Original Oberon camp around Prof. Wirth) was not increased performance (an argument that is now muted anyway), more convenient notation or increased security, but the simple fact that the OOP style – and therefore upcalls – should be used only when needed, i.e. it should be rare. The basic point was that upcalls should be used sparingly, and so the question remained open whether the (remaining) arguments of notational convenience and increased security justify the conceptional complication in those few cases where OOP is used. It was – righty – added that one should not go crazy about classes and methods. Not everything is a class. Smalltalk went too far.
A final comment on the discussions that took place at ETH Zurich in the early 1990s regarding OOP. I always found it a pity that the particual way of using the OOP shown in the Draw editor, has not been more widely “marketed” in the academic and industrial community, e.g., in courses or in text books. Even today, most books still focus on the two “basic” methods for doing OOP, namely: installing procedure handlers and message passing - without pointing out the subtleties and flexibility offered by the approach employed in the Draw editor. It’s a kind of failure in education.
The discussion that went on in the early 1990s were unique for several reasons: First, the fact that they took place at all – one could have simply followed to “industry trend” towards Smalltalk and C++ (remember that Java was not even in the picture back then..). Second, the fact that they were carried to the larger student body during the weekly recitation sessions typically led by the assistants of Profs. Wirth and Mössenböck. Very often, a sentence in such a session would begin with the words: “But Prof. Wirth contends that .. [blah blah] .. is not really necessary, because .. [very profound statement] .. On the other hand, Prof. Mössenböck says that .. [blah blah] .. may be really useful. Now, I want YOU to develop an informed point of view and articulate a valuable contribution to this hot discusssion..”.
It was a good time to be at ETH, and the education was (arguably) better than anywhere else - precisely *because* the folks there did not simply adopt the academic or industry mainstream (which mostly consisted C++ at the time), but made everyone not only deeply think about the core issues, but also *challenge* them. It was required.
But one must be honest. Oberon did not become mainstream. Period. By 1995, when Java arrived, the debate was over and the proverbial last nail was finally put in Oberon’s coffin - the jewel was lost (for now).
This affected the personal decisions of a great number of people. Personally, when I saw in the early 1990s that Oberon was not going to become mainstream, I completely lost interest in it for over two decades. I was not even thinking about following the Oberon-2 group that had formed around Prof. Mössenböck to the town of Linz (although I am from there originally), but went to MIT and later Silicon Valley instead. This was a true “cultural shock” to say the least!! But at least it was mainstream, and that’s really where the action is happening – however imperfect it might be. All major technologies now come out of Silicon Valley.
But at least many ideas that were already mainstream in the “golden Oberon era” are now making it into modern-day languages: strong type safety with *no* exeption whatsoever (not even type inclusion which was a bad idea to start with when it was introduced – and later discarded – in Oberon-1), the module concept done right (i.e. with type checking across module boundaries), a sound language definition based on mathematical principles, etc. Witness the “Swift” programming language for example and in particular the “Swift evolution” project. However, the process there is slow and – very - tedious. I feel I’m just waiting for everyone to catch up. But the main crux always critized by Prof. Wirth remains: these languages are still too complex. Why again are there separate concepts for structs (=records), classes (=pointers to records), modules, and protocols, and extensions, and and .. in Swift, when exactly two would do (records for data structuring and modules for information hiding)?
With the revival of Oberon in 2013 and the availability of emulators running on modern-day machines, it has become possible to play with Oberon again.
So let’s hope that more ideas will “spill over” to the “real” world. Eventually anyway..
Josef Templ josef.templ at gmail.com
Wed Jan 4 14:11:32 CET 2017
Excellent summary by Andreas.
In addition I would like to point out that the motivation of moving from Object Oberon's class syntax to the Oberon-2 syntax was to avoid many forward references. The other motivation was of course to make the syntactical differences smaller by removing additional keywords. The overall compiler changes for Oberon-2 were smaller than for
Object Oberon, if I remember correctly.
In later Oberon extensions such as BlueBottle and A2 the problem with forward references has been solved by substantially rewriting the parser but I don't know the details of the approach chosen. Having methods nested within a RECORD scope was thereby less of a burden in practice and very natural with respect to scoping.
In general, the message records and explicit message dispatching as used in the Oberon system proved to be successful and have also been used in BlackBox for the visible objects (Views), although BlackBox does not use handler procedure variables per object but type bound handlers.
Class based OOP with method tables instead of message records is more efficient because it replaces the message dispatching consisting of cascades of nested IFs by a tiny constant time operation. It gives up the full flexibility and extensibility, though.
This OOP style has been used in the "Draw" editor for abstracting from the different classes of graphical objects (Rectangles, Curves, etc.). The Draw editor was used for drawing board layouts with hundreds or thousands of graphical elements and at least at that time the performance gain was important.
Andreas Pirklbauer andreas_pirklbauer at yahoo.com
Tue Jan 3 12:44:01 CET 2017
This topic has been the subject of intense debate at the Institute of Computer Science at ETH Zurich around 1988-early 1990s. It's hard to convey the full spirit of these discussions so many years later, but I'll try to summarize some of the key aspects that were discussed back then, at least as far as object-oriented programming is concerned. One key topic was of course whether the language should be "instance-centered" vs. "class-centered".
First, a language called Object Oberon was devised and implemented at ETH Zurich. It was essentially a class-centered derivative of the original Oberon language (adding a keyword CLASS and class methods, etc). I have used it extensively. Among other things, that project demonstrated that adding a class mechanism to an existing language can be achieved with relatively little extra effort. And indeed, the compiler's complexity increased only moderately (10-20%). Then Oberon-2 was devised, which essentially removed the CLASS keyword and added type-bound procedures instead, i.e. classes where now again also called what they really are - namely records. It too was not significantly more complex than the original Oberon language and compiler. Of course much simpler than the OOP languages that emerged during that period (we were still in a pre-Java world back then and had to put up with C++ or the like).
But even though it was demonstrated that the additional complexity of the compilers for Object Oberon and Oberon-2 remained within tolerable bounds, the debate was not about that, but rather whether the notational conveniences justify the conceptual complications.
The "defendants" of the original Oberon language (instance-centered with procedure variables) argued that while it is true that class-centered languages (with class methods, a.k.a. type-bound procedures) offer a somewhat more convenient notation, increased security, and also more efficient upcalls, this was considered a marginal advantage. It was believed the object-oriented programming style should be confined to a few select cases only, i.e. one should use it only where it is justified and deemed appropriate - whatever that means (seen from today's point of view, where Java has achieved such a dominant position, and where it seems that almost everything has to be a class, this may sound strange, but back then the OOP style has not yet gained widespread adoption and many ideas were still in flux). The "case study" cited as proof was of course the Oberon System itself: It showed that the entire Oberon system could be programmed in conventional programming style, except for one area: the viewer system, as everyone knows who contributed to or extended the viewer system.
It was a lively debate, to say the least.. But in the end, the question remained open. This is, I believe, one of the reasons why we now have two main version of the Oberon language: Oberon and Oberon-2.
Personally, I believe Oberon7 is enough for many purposes, and that certainly includes system design. And that is why I decided to stick to the original Oberon language in Experimental Oberon. But that is entirely my personal bias. From an educational point of view, I would consider using Oberon-2 instead, as it very nicely and cleanly demonstrated the various OOP concepts. But in any case, more important than the choice of the language is, I believe, to teach the debate.
Srinivas Nayak sinu.nayak2001 at gmail.com
Tue Jan 3 05:17:06 CET 2017
In Oberon we have Procedure variables in Records.
In Oberon2 we added/appreciated Class Methods.
But Oberon7 we only retained Procedure variables in Records.
In the evolution of Wirthian languages, when Oberon allows object oriented paradigm, why still we thought, Procedure variables in Records is the way to go with Oberon7?
In other words, why we decided and retained Procedure variables in Records, but didn't go for Class Methods? Any disadvantage of Class methods?
Why we think, Procedure variables in Records is more elegant than Class Methods?
Where can we find discussions/decisions on these two constructs by masters?
Kindly note that, in this query,
my focus is on Language design, rather than Language use.
With thanks and best regards,
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Oberon