Topic: A simple but useful proposal


Author: Michael Spertus <mps@geodesic.com>
Date: Mon, 4 Apr 1994 22:10:01 GMT
Raw View
>>meaning that making a change to the private implementation of a
>>class often involves examining the code of all these members, even though
>>many of them probably don't use the private interface to the class at all.
>
> Most of the ones that dont require membership should be globals.
>

This isn't clear.  Let's look at some examples.

I suspect this first example illustrates the point you made that it
is not enough just to include a basis.

class interval {
public:
 double left();
 double right();
 double length();
};

This class represents an interval in the real number line.  Only two of
the above functions will be accessors, and the other will exploit the
relationship length = right - left.  Clients of the class should not
be required to know which ones the implementation chose to be actual
accessors.  So the third should be a guest.  Remember that clients of
the class don't distinguish between guests and publics.

> Global functions are the way to go to provide
> generic algorithms that work for all subtypes of a type.

I disagree.  The algorithms should be members so that subclasses
can reimplement them if they can make use of their extra structure.
(i.e. build specializations or curryings)
Frequently derived types replace generic methods that work for all
subtypes for efficiency reasons.  This is one of the reasons for
predicate classes in Cecil, which are a powerful language feature
(Of course, they provide other benefits as well).

For example, in the screen example, the home command belongs in
the interface to screen to give particular terminal types (such as
VT52, etc.) that have specific escape sequences, etc. for the home
command to use them while letting less capable displays simply inherit
the generic method.

Perhaps the most important reason to not use globals for things that
could be members is that it can not usually be stated with certainty
that some relation will hold in all subtypes, so an interface
should be provided to the class that lets clients explain why they
need their information.  This approach leads to many guests, but
drastically increases reuse of both the class' code and client code.

For example, a person class will usually have a gender method.  If
we add a gender_at_birth method to the class as a guest, this lets
clients explain their request more thoroughly (now gender alone
provides the interface of current gender.)  Now if you are using a
database of musical artists and are studying the effect of gender
on success in the music industry, you will be able to add a subclass
to hold Wendy (nee Carlos) Walters of Hooked on Bach fame, not to
mention Ru Paul, without busting either your Womens Studies software,
your person class or introducing inaccuracies.  Perhaps when biohacking
becomes easy a few years from now, many people will change their gender
as frequently as their hairstyle.

The point is that rich class interfaces allow clients of classes to
make detailed requests of the class that are more likely to survive
unanticipated changes in the understanding of the classes.  It is my
experience with maintainability, that most maintenance changes were
unanticipated in the original design.

Creation of rich class interfaces to allow specificity of requests
which eliminate the need for clients to assume relationships among
requests almost requires the presence of a guest modifier.  The non-
tyrannical nature of C++ means that it should be compatible with
this clearly reasonable OOA/OOP philosophy.

Similarly, C++'s non-tyrannical power is a prime cause of its success,
because it lets you use designs from C and designs from object-oriented
programming languages side-by-side.  Requiring people to use global
functions to avoid needing guests prevents people from using
Smalltalk/Objective-C type designs in C++.

> ... some people have the idea that ... global functions invariably
> cause global namespace pollution (which they don't).

True.  They don't invariably, but in practice they usually do.  This
isn't always bad design (but even where it is, making bad design less
of a temptation isn't always a bad idea) but frequently it is just
that the size of the need for robustness down the road isn't always
fully anticipated.  e.g., if the internet becames an online object
base for the country, existing C++ programs will require less modification
if they have a more method than global function based approach.

Smaller versions of this problem happen repeatedly as code meant for
prototyping frequently gets put into production code for years.

In sum, there are genuine advantages to the guest proposal and it appears
to have no downside.  It doesn't break a single line of existing code, and is
trivial to understand, use, and implement.




Author: h.b.furuseth@usit.uio.no (Hallvard B Furuseth)
Date: 5 Apr 1994 17:15:32 GMT
Raw View
In article <CnEMy1.KwA@world.std.com> miket@world.std.com (Michael Trachtman) writes:

> The trick is to place the private,protected and public parts in one
> class with the name XXX_core. and the 'guest' part in a class XXX
> derived from XXX_core.

... and you may also double the run-time needed for vtable setup, and
you need to copy the header of XXX_core constructors into the derived
class (or are constructors inherited now?), where just one keyword would
to the same job.

> Or how about 'const public'.

Yes, I have wondered about that.  Why not 'const public' (and 'const
protected')?  It seems like such an obviously useful idea; surely the
committee has considered it?

Quite a number of my class bodies are much larger than necessary, just
because I need a bunch of inspector functions.

--
Hallvard




Author: pyungy@minerva.rolm.com (Pyung H. Yoon)
Date: 6 Apr 1994 00:39:09 GMT
Raw View
Regarding the proposal, in short I would like to have "guest" specifier
if I have to maintain an inheritance tree of say 10 levels, which is designed
by someone else and already went through a couple of maintenance rounds
before I took over.

Let's think about the case where I have to modify the internals of a class in
5th level. As long as I keep the public interface unchanged, I don't have to
worry about the guest functions in dervied classes. This can save me some
headaches if the inheritance tree has a large number of guest functions.
I know the scope of change and the scope is narrower when compared to the
case where no "guest" specifier is used. And I don't have to worry about
testing the guest functions, at least in theory.

I always thought that the inheritance and polymorphism are the best friends of
OO programmer. However, I am also afraid that they can be the worst nightmare of
maintenance people, especially when the inheritance tree is deep and documentation
is not complete.

But don't get me wrong. Still it's much better to maintain than a bunch of C
functions and global variables AND poor documentation.

-Pyung-





Author: nvkumar@icaen.uiowa.edu (Neel Vasant Kumar)
Date: 7 Apr 1994 15:59:19 GMT
Raw View
[Whole discussion about guest functions deleted]

I think that we are confusing the issue of language v. development
environment.  Since the birth of programming, I think we have had a unified
concept 'cause whatever little support the poor programmer had, it all came
from the language.  However, we are now seeing things which impact
maintainibility, something that is not 100% language based....

Maybe we need a development environment standard, something that would
incorporate features such as compile time exception signature checking,
guest functions etc etc and leave the language to do the basic things.
Think of it as "RISC" language....

Just a thought...
--
Neel Vasant Kumar       |  #include <disclaimer,h>
nvkumar@icaen.uiowa.edu |  Don't discriminate, lest you be discriminated....




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 10 Apr 1994 00:15:53 GMT
Raw View
In article <1994Apr4.221001.19032@midway.uchicago.edu> Michael Spertus <mps@geodesic.com> writes:
>
>I suspect this first example illustrates the point you made that it
>is not enough just to include a basis.

 Its _enough_. That is, its necessary and sufficient,
but not necessarily the best design.

>
>class interval {
>public:
> double left();
> double right();
> double length();
>};
>
 Yes, that looks like a reasonable class.

>> Global functions are the way to go to provide
>> generic algorithms that work for all subtypes of a type.
>
>I disagree.  The algorithms should be members so that subclasses
>can reimplement them if they can make use of their extra structure.
>(i.e. build specializations or curryings)

 Globals can be specialised by overloading, in addition,
they can dispatch to specialised methods. So they are suitable
for algorithms that CANT take any further advantage of the
representation details.

 Often, the improvements will be in the algorithm itself,
and we dont want to reimplement the algorithm wherever some
minor improvement was made by a specialisation, but a change
of algorithm is a major improvement.

 For this to be effective, you do need to choose a
good representative set of properties for your class.

 But this technique (using globals) is not optional,
its mandatory. The open/closed principle demands globals:
you cant close off the class and be done with it if every
algorithm you write has to be added as a new member function.

>Frequently derived types replace generic methods that work for all
>subtypes for efficiency reasons.

 Yes, but you must limit the number of such methods
or you cant finish off the class interface permanently.
To do that, you do need to choose a good set of fundamental
properties. This is what mathematics is all about:
deriving a FEW powerful theorems, from which much else can
be computed efficiently.

>Perhaps the most important reason to not use globals for things that
>could be members is that it can not usually be stated with certainty
>that some relation will hold in all subtypes,

 You MUST.

so an interface
>should be provided to the class that lets clients explain why they
>need their information.  This approach leads to many guests, but
>drastically increases reuse of both the class' code and client code.
>
>For example, a person class will usually have a gender method.  If
>we add a gender_at_birth method to the class as a guest, this lets
>clients explain their request more thoroughly (now gender alone
>provides the interface of current gender.)

 No, this is a bad example. Gender at birth can ALREADY
be determined, otherwise gender_at_birth is not a guest but a
new primitive -- indeed a new basis method.

>The point is that rich class interfaces allow clients of classes to
>make detailed requests of the class that are more likely to survive
>unanticipated changes in the understanding of the classes.  It is my
>experience with maintainability, that most maintenance changes were
>unanticipated in the original design.

 I have the opposite experience: minimalist classes
are much simpler to use and maintain. They're easier to design,
and it MUCH easier to stuff up a design with lots of methods.
>
>Similarly, C++'s non-tyrannical power is a prime cause of its success,
>because it lets you use designs from C and designs from object-oriented
>programming languages side-by-side.  Requiring people to use global
>functions to avoid needing guests prevents people from using
>Smalltalk/Objective-C type designs in C++.

 Yes, I'd discourage RTTI based coding. Its usually
symptomatic of faulty design. (Not always).
>
>> ... some people have the idea that ... global functions invariably
>> cause global namespace pollution (which they don't).
>
>True.  They don't invariably, but in practice they usually do.

 You missed the bit in brackets, so I'll shout.
THEY DONT CAUSE GLOBAL NAMESPACE POLLUTION AT ALL.

 A function f(T*) causes NO conflicts with a function
f(X*) when T and X are distinct classes. Using overloading
like this is just as safe and non-polluting as adding members.

 The worst one can say is that, since the function
is not encapsulated in the class, one might have already
defined f(T*) elsehwere.

 In fact, this is a common error IN CLASS HEIRARCHIES.

 Namespace pollution happens with variables, enums, type names,
and functions NOT sensibly related to classes -- including
standard type like int/long etc.

>In sum, there are genuine advantages to the guest proposal and it appears
>to have no downside.  It doesn't break a single line of existing code, and is
>trivial to understand, use, and implement.

 It has a downside: adding complexity, however small, to the
language.

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 10 Apr 1994 00:49:08 GMT
Raw View
In article <2o1akn$4lj@news.icaen.uiowa.edu> nvkumar@icaen.uiowa.edu (Neel Vasant Kumar) writes:
>[Whole discussion about guest functions deleted]
>
>I think that we are confusing the issue of language v. development
>environment.  Since the birth of programming, I think we have had a unified
>concept 'cause whatever little support the poor programmer had, it all came
>from the language.  However, we are now seeing things which impact
>maintainibility, something that is not 100% language based....
>
>Maybe we need a development environment standard, something that would
>incorporate features such as compile time exception signature checking,
>guest functions etc etc and leave the language to do the basic things.
>Think of it as "RISC" language....
>
>Just a thought...

 There is an ISO (Draft I think) Standard called PCTE.
--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: grumpy@cbnewse.cb.att.com (Paul J Lucas)
Date: Fri, 25 Mar 1994 16:51:21 GMT
Raw View


Author: bill@amber.csd.harris.com (Bill Leonard)
Date: 25 Mar 1994 21:58:56 GMT
Raw View
In article <1994Mar24.004441.140@midway.uchicago.edu>, Mike Spertus <mps@geodesic.com> writes:
> The Motivation:
> ===============
>
> Everyone agrees that having private members of classes is good, because they
> mean that if you change the internals of a class, you don't need to analyze
> every function that uses the class to see if it is affected by the change,
> but only its members and friends.
>
> However, many functions are made members of a class, because that is the
> natural place for them, not because they require access to the private
> members.  For example, in Lippman's C++ Primer, he gives a Screen class
> with a public Move method.  He also gives a Home method, which expands to
> Move(0,0).  The Home method clearly belongs in class Screen, even though
> it doesn't need access to private members of Screen.

I don't know that Home "clearly" belongs in class Screen.  Perhaps it would
be better to create a namespace containing Screen, and put things like Home
in that namespace.  As far as I can see, the only difference would be that
you'd have to explicitly declare Home as taking an argument of type Screen
*, and call Home as "Home(object)" instead of "object->Home()".

This seems analogous to the practice of making operators like "+" not be
members, but making "+=" a member and having "+" use "+=".  Namespaces make
it easier to manage these "non-member members".

--
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL  33309
bill@ssd.csd.harris.com

These opinions and statements are my own and do not necessarily reflect the
opinions or positions of Harris Corporation.

------------------------------------------------------------------------------
"I can do trivial things pretty trivially."
                                                  Tom Horsley
------------------------------------------------------------------------------




Author: miket@world.std.com (Michael Trachtman)
Date: Sat, 26 Mar 1994 12:26:42 GMT
Raw View
Bill Leonard (bill@amber.csd.harris.com) wrote:

[scrunch]

: This seems analogous to the practice of making operators like "+" not be
: members, but making "+=" a member and having "+" use "+=".  Namespaces make
: it easier to manage these "non-member members".

Are namespaces allowed within classes ??

I.e. can you do something like: (I apologize if I botched the syntax
  since I've never actually used namespaces.)

class BIG_THING {
  private:
    ...
  public:
     namespace SERIOUS_INTERFACE {
        void dont_smile() {}
        void be_somber() {}
     }

     namespace FUN_INTERFACE {
        void have_fun() {}
        void be_merry() {}
     }
}

use BIG_THING:FUN_INTERFACE;

...


Michael Trachtman




Author: grumpy@cbnewse.cb.att.com (Paul J Lucas)
Date: Sat, 26 Mar 1994 15:31:24 GMT
Raw View


Author: Mike Spertus <mps@geodesic.com>
Date: Thu, 24 Mar 1994 00:44:41 GMT
Raw View
The Motivation:
===============

Everyone agrees that having private members of classes is good, because they
mean that if you change the internals of a class, you don't need to analyze
every function that uses the class to see if it is affected by the change,
but only its members and friends.

However, many functions are made members of a class, because that is the
natural place for them, not because they require access to the private
members.  For example, in Lippman's C++ Primer, he gives a Screen class
with a public Move method.  He also gives a Home method, which expands to
Move(0,0).  The Home method clearly belongs in class Screen, even though
it doesn't need access to private members of Screen.

It is not at all uncommon now for classes to have dozens (or hundreds) of
members, meaning that making a change to the private implementation of a
class often involves examining the code of all these members, even though
many of them probably don't use the private interface to the class at all.

The Proposal:
=============

A member of a class is a "guest" if it is publically accessible but does not
require access to the private or protected members of the class.  The term
"guest" is appropriate, because guests can be in your house, but shouldn't
look at your private stuff.  Guest would be used as an access specifier the
same way as public or private.  The Screen example would become

class Screen {
public:
 move(int x, int y) { X = x; Y = y; }
guest:
 home() { move(0,0); }
private:
 int X, Y;
};

It would be a compile-time error to make move a guest.

Inheritance makes things even worse, because if you modify the protected
interface to a class, you have to examine all the functions that are defined
in derived classes to see if they are affected by the change.

I've found both these cases to occur frequently in my programs, especially
because member functions should as a matter of style generally use the
public accessors to private attributes when they exist.

I know that one can simulate guests by creating an extra layer of
inheritance, but this is artificial, and doesn't handle protected members
correctly. Anyways, I've rarely seen this done, while I've frequently
run into cases where guests would have
saved me a lot of tedious work.  It doesn't even need to prevent people
from using "guest" as an identifier, because an identifier name followed by
a colon would be illegal at that point.  No existing code would need
to become invalid.  Of course, if the committee wanted
to, it could disallow "guest" as an identifier, the way it does "public" and
"private".

I know that this is a simple proposal, and people here seem to be more
concerned with adding powerful and subtle new features to the language, but
simple changes can be very useful and don't require the addition of a lot
of implementation or use complexity, and they may not even require endless
controversy over the right form of them.