From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,47def5aa7b3182bd X-Google-Attributes: gid103376,public From: Samuel Mize Subject: Re: (shorter and new) programming by extension Date: 1999/02/25 Message-ID: <7b49mm$t7q@news1.newsguy.com> X-Deja-AN: 448448277 References: <79fct8$9k3$1@murdoch.acc.Virginia.EDU> <1103_918264881@DZOG-CHEN> <36cdb012.580716@news.pacbell.net> <7av52o$62g@news3.newsguy.com> <7avc0i$cnb$1@its.hooked.net> <7b1kdb$13k6@news3.newsguy.com> <7b22f5$1mrg@news1.newsguy.com> Organization: ImagiNet Communications, Ltd. User-Agent: tin/pre-1.4-981002 ("Phobia") (UNIX) (AIX/3-2) Newsgroups: comp.lang.ada Date: 1999-02-25T00:00:00+00:00 List-Id: I'm too pedantic to leave adequate-enough alone, so I've refined my example of non-object-oriented programming by extension. I integrated that into the text I wrote on that earlier. I hope somebody out there finds this of interest. Sam Mize - - - - - - - - - - Programming by Extension means programming a large system so that new processing cases can be handled by adding new code, without having to recode any existing modules. Language support is extremely helpful for true programming by extension. Object-oriented development is a very specific way of analyzing, designing and coding a system. It provides a way to program by extension -- that's one of its selling points -- but it brings along a lot of other baggage too. Sometimes you want that baggage. That's why "object-oriented" technology exists. But sometimes you just want to do plain functional programming, and be able to extend the system later without having to recode the existing part. Ada supports this also. The Ada 95 Rationale describes how Ada supports programming by extension, but it doesn't really define the term. Here's a concrete example of programming by extension. Consider a function to read up to 20 messages from some interface, compute statistics and generate a summary. The rest of the system only cares about the summary; it doesn't care about the input messages, or about how the interfaces work. One simple approach would put the summarizer function in a separate package, with an enumeration variable to tell it which interface to use: package Simple_Approach is type Interfaces is (Disk_File, Serial_Interface, ...); type Summary_Type is record ... end record; function Summarizer (Interface_To_Use: Interfaces) return Summary_Type; end Simple_Approach; The problem, of course, is that adding a new interface will cause recoding of the type Interfaces and the function Summarizer, and recompilation of anything that "withs" package Simple_Approach. Note that Ada is often used in life-critical systems like avionics, where each unit goes through a long and expensive validation of the compiled object code. This must be redone if the unit is recompiled, even if the source code is not changed. Lets use programming by extension to prevent all this recoding and recompilation. The first question is, what needs to be extended? In this case, the operation of getting a message from an interface. So, we'll create a procedure Get that gets a message from an interface, basing it on a tagged type so it's polymorphic: with Message; package Generic_Interface is type Flag is tagged null record; procedure Get (Which_Interface: Flag; Data: Message.Data_Type); end Generic_Interface; We can now write Summarizer as a class-wide procedure, which uses dispatching to get messages from whatever interface is appropriate: with Generic_Interface; package Extension_Approach is function Summarizer (Interface_To_Use: Generic_Interface.Flag'Class) return Summary_Type; end Extension_Approach; - - - - - - with Messages; package body Extension_Approach is function Summarizer (Interface_To_Use: Generic_Interface.Flag'Class) return Summary_Type is Data: array (1..20) of Message.Data_Type; begin for I in 1 .. 20 loop Get (Interface_To_Use, Data (I)); exit when Data (I).Last_Message; end loop; ... The body of Get in the package Generic_Interface may return an exception, or get messages from a default interface (in which case the package should probably be named Default_Interface). To extend Summarizer to get messages from a new interface, we just extend the type Generic_Interface.Flag, and override its Get: with Message; with Generic_Interface; package Disk_Interface is type Flag is new Generic_Interface.Flag with null record; procedure Get (Which_Interface: Flag; Data: Message.Data_Type); end Disk_Interface; In no case will the type Flag contain any data. Now we can call Summarizer to get messages from disk and return us the summary: Summary := Extension_Approach.Summarizer (Disk_Interface.Flag'(null record)); Even if Disk_Interface was written after Summarizer, we don't need to recompile Summarizer. With a bit more care we can code almost all the user system so that it won't need to be recoded, or even recompiled, to handle any new interface. First we add a declaration to Generic_Interface that lets the user store a flag variable: type Interface_Selector is access constant Flag'Class; end Generic_Interface; In each interface package, we put a constant Selected, so the user won't have to use the rather clunky syntax "TYPENAME'(null record)". We also put an access constant that denotes Selected, which can be stored in a variable of type Interface_Selector. Selected: constant Flag := Flag'(null record); Selection: constant Generic_Interface.Interface_Selector := Selected'Access; end Disk_Interface; (These additions to the _Interface packages are just convenience items, the user code could make these declarations itself.) Now, to hard-code which interface is being used, the code can say: with Disk_Interface; with Extension_Approach; use Extension_Approach; procedure User is Sum: Summary_Type; begin Sum := Summarizer (Disk_Interface.Selected); To encapsulate the knowledge about interfaces in a single package, the code might look like: package Interfaces is Current: Generic_Interface.Interface_Selector; end Interfaces; - - - - - with Interfaces; with Extension_Approach; use Extension_Approach; procedure User is Sum: Summary_Type; begin Sum := Summarizer (Interfaces.Current); ... Now, to extend User, we only have to add another class, and somewhere there will be a bit of code -- possibly buried in the user interface -- that we will have to change so it can set Interfaces.Current to New_Interface.Selection. The entire rest of the system won't have to be recoded, or even recompiled. Another feature of the Programming-by-Extension facilities in Ada 95 is that, when you derive a new type from a tagged type, you can re-use the "parent" type's procedures, if they still apply. For example, suppose that some interfaces must be explicity started and stopped. The code might look like: with Message; package Generic_Interface is type Flag is tagged null record; procedure Get (Which_Interface: Flag; Data: Message.Data_Type); procedure Start (Which_Interface: Flag); -- does nothing procedure Stop (Which_Interface: Flag); -- does nothing ... - - - - - - with Message; package Disk_Interface is type Flag is new Generic_Interface.Flag with null record; procedure Get (Which_Interface: Flag; Data: Message.Data_Type); -- we don't need to start or stop a disk, so the do-nothing Start -- inherited from Generic_Interface is just fine ... - - - - - - with Message; package Serial_Interface is type Flag is new Generic_Interface.Flag with null record; procedure Get (Which_Interface: Flag; Data: Message.Data_Type); -- Start and stop the serial interface procedure Start (Which_Interface: Flag); procedure Stop (Which_Interface: Flag); ... And the user code would look like: with Interfaces; with Extension_Approach; use Extension_Approach; procedure User is Sum: Summary_Type; begin Start (Interfaces.Current); Sum := Summarizer (Interfaces.Current); Stop (Interfaces.Current); ... I hope you find this extended sketch useful. I would certainly NOT call this object-oriented programming. But it does take advantage of the Ada 95 facilities for programming by extension. It's sort of an "infinite case statement." Certainly this is a contrived example. I HAVE used tagged types to generate such an "infinite case statement," but it was buried in an obscure and proprietary system, so it would be neither clear nor legal for me to reproduce the code. You can do the same general things in C++, but you have to make the window type a class, and each new window type a subclass of that class. An instance of the class is an "object." The terms "class" and "object" carry a lot of connotations that may not be true in all cases, for instance that the item in question is a discrete entity that exists in its own right, or that the "classes" involved form a meaningful taxonomy and not just a hierarchy that reflects some implementation concern. Further, I believe there may be extra semantic constraints and extra coding effort in using C++ for non-object-oriented programming by extension. However, I am not familiar enough with the language to give you specific details. Sam Mize -- Samuel Mize -- smize@imagin.net (home email) -- Team Ada Fight Spam: see http://www.cauce.org/ \\\ Smert Spamonam