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: 1108a1,2c6139ce13be9980 X-Google-Attributes: gid1108a1,public X-Google-Thread: fac41,2c6139ce13be9980 X-Google-Attributes: gidfac41,public X-Google-Thread: 103376,3d3f20d31be1c33a X-Google-Attributes: gid103376,public X-Google-Thread: f43e6,2c6139ce13be9980 X-Google-Attributes: gidf43e6,public From: Erik Ernst Subject: Re: Interface/Implementation (was Re: Design by Contract) Date: 1997/09/04 Message-ID: X-Deja-AN: 269793737 References: <01bcb389$24f579d0$1c10d30a@ntwneil> <5u62es$e23$7@miranda.gmrc.gecm.com> <340C85CE.6F42@link.com> Organization: DAIMI, Computer Science Dept. of Aarhus Univ. Newsgroups: comp.object,comp.software-eng,comp.lang.ada,comp.lang.eiffel Date: 1997-09-04T00:00:00+00:00 List-Id: Samuel Mize writes: > Patrick Doyle wrote: > > [..] > > What can you do with a hand-written package spec that you can't > > do with the Eiffel tools? I can't resist the temptation to mention that there are other possibilities; you are comparing the two approaches: (1) Writing a complex entity [e.g. a class] in one modular unit [e.g. file]; possibly using tool support to extract aspects. This is the Eiffel way. (2) Splitting a complex entity into an external and an internal aspect [e.g. interface and implementation] by a language mechanism which requires repeating the skeleton in order to recombine the two separated aspects [e.g. routine signatures are duplicated]. This is the Ada way. (1) is said to be good because it avoids code duplication, giving better consistency and less tedium when writing/updating/managing. (2) is said to be good because it protects the interface from accidental or insignificant changes, probably leading to fewer unnecessary recompilations, and because programmers understand the role of an interface better when it exists separately. The itemized list by Samuel Mize quoted at the end of this posting give more examples of attractive possibilities. An obvious third approach (obvious because I'm using it every day ;-) uses a _separate_ (mini)language to handle modularization: (3) Using an orthogonal physical-organization language which allows splitting a complex entity expressed in the main language in many different ways, and which supports flexible visibility management among these modular units. The recombination of the separate code fragments has minimal duplication: each code fragment has a name, and the place where it belongs is marked with that name. Eiffel does not support physical separation of different aspects of the source code (such as interface/implementation). Assuming that such a physical separation is actually valuable, we could make the experiment of putting it in! From now on I use the word "file" to refer to whatever modular unit the language environments uses. The class: ---------------------------------------- class PERSON2 feature name: STRING loved_one,landlord: PERSON2 set_loved(l: PERSON2) is -- attach the loved_one field of current object to l. do loved_one := l end end ---------------------------------------- could be fragmented into an interface part and an implementation part: ---------------------------------------- class PERSON2 feature <> set_loved(l: PERSON2) is -- attach the loved_one field of current object to l. <> end end ---------------------------------------- <> name: STRING loved_one,landlord: PERSON2 <> do loved_one := l ---------------------------------------- Think of it as search'n'replace: put the code 'Fragment' where the 'PlaceHolder' appears. 'Effective' says that the "unknown piece of code" is syntactically derived from the nonterminal 'Effective'. This ensures that syntax errors and ambiguities cannot arise when putting the pieces together. A good compilation system would of course not actually do the search'n'replace---clients should not be recompiled just because the implementation changes---but the semantics should be as-if. For some purposes, we might prefer to link the PERSON2 interface with another implementation: ---------------------------------------- <> name: STRING landlord: PERSON2 loved_ones: LINKED_STACK[PERSON2] -- ought to make this non-void somewhere ;-) <> do loved_ones.push(l) ---------------------------------------- This is a compile-time choice between two different implementations. If inheritance is preferably used for conceptual modelling, there is a need for a separate mechanism for such things---using inheritance to choose between two different implementations would pollute the inheritance hierarchy with implementation considerations. Moreover, the polymorphism implied by the inheritance approach would prevent a fast, static routine dispatch, which could be achieved with the 'Fragment'/'PlaceHolder' driven approach. There is a too-much-generality cost when using inheritance, one might say.. Of course, the standard Eiffelish reaction would be "there should not be any other notion of module than that of a class", and perhaps "short/flat is at least as good a solution as having physical separation of interface and implementation", so I'm presumably mostly speaking to the Eiffel underground, if such one exists ;-) Anyway, it works perfectly well in BETA, and it certainly supports the below mentioned cases (though "Multiple implementations, run time" would also be handled with polymorphism in BETA, and "Multiple implementations, item-specific, compile time" would be handled with virtual patterns). Here are all the interesting challenges, as written by Samuel Mize: > I'm not familiar with Eiffel. Let me tell you a few things we > do in Ada, and you can tell me if the tools support it or not. > I'd be quite interested to know how the short-flat extraction tool, > or the deferred class approach, address these. NOTE: I'M NOT > SAYING THEY CAN'T, I'M ASKING HOW THEY DO IT. Please don't > assume this is an attack; it isn't. I'ts a request for info. > > Please specify whether the practice you suggest is commonly > done. I've listed things that I've seen done several times for > real production code. I'm especially curious if the deferred-class > approach that Patrick Doyle suggests is a common idiom in Eiffel. > > Some capabilities that are provided by Ada's separate spec/body > approach are: > > * Different order of subprograms -- one order or grouping may be > most meaningful for the interface, another for implementation. > > * Unexported (local to the package) subprograms and declarations. > > * Different comments -- the Ada spec usually contains comments that > describe what facility a subprogram provides and how to use it, > the package contains a description of its implementation details. > > * Interface control and verification -- the Ada spec can be written > and published, and other groups can code against it. If the > implementation or the client changes, the compiler can verify that > they are still consistent without having to reprocess both. > > * Multiple interfaces -- you can have several sub-packages that > export subsets of a package's functionality. In Ada 83, if > visibility into the body (implementation) is required, these > must all be contained in the main package's spec; in Ada 95, > they can be separate child packages. In either case, if you > just want to re-package existing functionality to provide > different views, you can provide "view" packages that rename > elements from the main ("full-view") package. > > * Multiple implementations, global, compile time -- you can have > many implementations of your package spec, and select the best > one for your application at compile time. Thus, your "list" > could have a hash table for faster lookup, or store its contents > to disk to allow long lists of huge data items (just one in > memory at a time), or whatever -- as long as the spec stays the > same, the client code doesn't need to be recompiled. > > * Multiple implementations, item-specific, compile time -- > Ada generics can provide a unified interface for several > implementations of a facility. Any logic that is common across > all the implementations is written into the generic, while any > code that is implementation-specific is provides as subprograms > that are parameters to the generic. In the worst case, the > generic only provides a "template" that instantiations must > follow. For example: > > generic > type Item is private; > type Stack is private; > with procedure Push (S: in out Stack; I: in out Item); > with procedure Pop (S: in out Stack; I: in out Item); > package Generic_Stack is > -- exports the procedures imported > procedure Push (S: in out Stack; I: in out Item); > procedure Pop (S: in out Stack; I: in out Item); > end Generic_Stack; > > This generic just encapsulates an interface. If you write clients > that instantiate it, and implementation packages that can be used > to instantiate it, then you know any client can switch to any > implementation with a simple, compile-time change to the client > code (where it instantiates the generic). > > * Multiple implementations, run time -- these are best supported in > Ada by dispatching subprograms and tagged types, not by packaging. cheers, -- Erik Ernst eernst@daimi.aau.dk Computer Science Department of Aarhus University, Denmark Check