[comp.lang.eiffel] Suggestion for use of Void

rick@tetrauk.UUCP (Rick Jones) (11/30/90)

This newsgroup seems to have been very quiet recently - is Frank Caggiano the
only person (apart from me) writing Eiffel code?  Just to liven things up, here
is an idea for discussion.  (For the record, I thought Frank's stuff on X
resource management looked good and is a much needed facility.  I've saved it
for later since I'm not doing any X windows work right now.)

(BTW, did my posting on an enhanced exception class finally make to the net?
Our feeder site had some serious disk problems recently and a lot of stuff got
lost.  I would have expected some comment on the article & I haven't received
any - could anyone who's seen the article let me know.  If I get no replies I
will try and post it yet again.)

This article concerns the use (or lack of) the Void keyword in Eiffel.  I
believe it would be useful if the language supported Void as a "stand-alone"
keyword, representing of course a void reference.  While the language currently
has .Void and .Forget as pseudo features representing test and assignment
against a void value, it doesn't have a construct to pass a literal void
reference as an argument to a routine.  I now have a number of classes with
features which will accept a void argument and still behave sensibly;  the
pupose of the parameter is to supply optional information.  A particular
example is an exception object which can be initialised with arbitrary comment
text at the point of creation:

	local
		excep: MSG_EXCEP ; code: INTEGER ;
	do
		...
		if <some condition> then
			excep.Create (code, "Report this error") ;
		...


When used in a context in which no information is relevant, it is necessary to
declare a local string variable to provide a syntactically valid void
reference:

	local
		excep: MSG_EXCEP ; code: INTEGER ; null_str: STRING ;
	do
		...
		if <some condition> then
			excep.Create (code, null_str) ;
		...
(I specifically want a void reference, not a blank string)

It would be far cleaner to be able to write:

			excep.Create (code, Void) ;

A Void of course conforms to any class type, so it is syntactically valid
anywhere an object reference is required.

This raises the interesting issue of forms such as:

	obj_ref := Void			equivalent to	obj_ref.Forget

	if obj_ref = Void then		equivalent to	if obj_ref.Void then

These would clearly be valid, and as alternate forms are they preferable to the
current style of pseudo features?  My personal opinion is that they are, since
I find the pseudo feature concept in Eiffel one of its less elegant
characteristics.  Given the opportunity, I would in fact replace the following
as well:

	a.Create (params)		a := Create A_CLASS (params)

	a.Clone (b)			a := b.Clone

In the latter case, the feature deep_clone which is in ANY works the same way
as the right-hand version, making the different clone operations incompatible
in the current version.  In fact if deep_clone were called "duplicate", it
would I believe support the "duplicate" feature of the STRING class without any
redefinition.

Does anyone else have a view on this?  Does anyone care?
Is there anyone out there?  Is the Eiffel tower still standing?

-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan

tom@eds.com (Tom H. Meyer) (12/04/90)

In article <1037@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:

>It would be far cleaner to be able to write:
>
>			excep.Create (code, Void) ;
>
>A Void of course conforms to any class type, so it is syntactically valid
>anywhere an object reference is required.
>
>This raises the interesting issue of forms such as:
>
>	obj_ref := Void			equivalent to	obj_ref.Forget
>
>	if obj_ref = Void then		equivalent to	if obj_ref.Void then

I have often run into this problem and wished for exactly this solution. Oh,
well. So much for sparking a controversy :-)

>...  Given the opportunity, I would in fact replace the following
>as well:
>
>	a.Create (params)		a := Create A_CLASS (params)

I dislike this on the grounds that it would *require* that 'Create' be a
keyword. I hope to see the day when classes can have more than one
creation routine and this seems to disallow it.

Since you asked for some net traffic and we're talking about programming,
I would like feed back on how some others out there would do the following:

Suppose I have some class which which has a list of generic objects and
my application needs to examine every item on the list and perform 
different actions based on the actual type of the items on the list.

My solution is a sequence of reverse assignment attempts to,
first, determine the actual type of the object, and, second, have a
local object of the correct type to access the specific object's features.

I've heard that this is poor style but I've never been able to see a
better way to do it. Supposedly, there is some way of designing the classes
so that this isn't necessary. Can anyone give me an example?


tom meyer, EDS Research               | If I don't see you in the future
...uunet!tantalum!tom                 | I'll see you in the pasture
505.345.2563

rsw@cs.brown.EDU (Bob Weiner) (12/04/90)

In article <4106@tantalum.UUCP> tom@eds.com (Tom H. Meyer) writes:
>
> Since you asked for some net traffic and we're talking about programming,
> I would like feed back on how some others out there would do the following:
> 
> Suppose I have some class which which has a list of generic objects and
> my application needs to examine every item on the list and perform 
> different actions based on the actual type of the items on the list.
> 
> My solution is a sequence of reverse assignment attempts to,
> first, determine the actual type of the object, and, second, have a
> local object of the correct type to access the specific object's features.
> 
> I've heard that this is poor style but I've never been able to see a
> better way to do it. Supposedly, there is some way of designing the classes
> so that this isn't necessary. Can anyone give me an example?
> 

The key question is whether you know at class design time whether or not
objects of the class type will be used on the list.  Even if you don't,
you could still define a somewhat generalized protocol (varying numbers
of parameters are still a problem) in which all of your non-generic
classes have an arbitrary 'action' procedure.  Then when you pop an
object off the list into an entity of type ANY, simply invoke
'entity.action(params)'.  Eiffel's dynamic binder will do the rest.

My guess is that your problem is more complicated than this, so you will
have to elaborate some more.  Your problem does demonstrate a weakness
of current object-oriented languages that I have recently been
investigating.  I call it the 'context conditional' problem.

Dynamic binding solved the 'typecase conditional' problem, allowing one
to work with an open ended set of types all of whose features are
properly invoked at runtime, without the need to modify completed source
code in order to add a type.

But today's widely-used OO languages have no mechanism for dynamically
binding features based upon contexts, where a context is defined as some
set of constraints on the program state at runtime.  Thus, one still
needs code of the form:

	if var1 = 10 and flag1 = true then
	     obj.action1
	elseif var1 > 10 and flag2 = false then
	     obj.action2
	...
	endif

This yields a similar problem as one had with embedded typecase
statements.  That is, every time a new constraint type is added one must
modify code possibly spread throughout a number of classes.

The solution I am working on involves multiple class protocols called
views coupled with contextually linked constraints.  Details are too
lengthy and not yet complete enough to go into here, but I wanted to
mention these ideas as possible long term solutions to the sort of
problem you are now faced with.

--
Bob Weiner				   rsw@cs.brown.edu

pw@tnoibbc.UUCP (Peter Willems) (12/05/90)

>In article <1037@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:
>>A Void of course conforms to any class type, so it is syntactically valid
>>anywhere an object reference is required.
>>
>>This raises the interesting issue of forms such as:
>>	obj_ref := Void			equivalent to	obj_ref.Forget
>>	if obj_ref = Void then		equivalent to	if obj_ref.Void then

I seem to remember that Bertrand Meyer introduced for this same issue
the idea of a class NONE.  Conceptually class NONE inherits from all other
classes and therefore conforms to any class type.  Of course there is no
real difference with the Void concept, it is slightly more elegant I think.

Probably this enhancement will be part of Eiffel 3.0.

-- 
Peter Willems : TNO - IBBC                    INTERNET : pw@tnoibbc
              : PO-box 49                     DOMAIN   : pw@ibbc.tno.nl
              : 2600 AA  Delft                FAX      : +31 15 843990
              : the Netherlands               VOICE    : +31 15 842032

tom@eds.com (Tom H. Meyer) (12/05/90)

In article <RSW.90Dec4075159@tahiti.cs.brown.EDU> rsw@cs.brown.EDU (Bob Weiner) writes:
>The key question is whether you know at class design time whether or not
>objects of the class type will be used on the list.  Even if you don't,
>you could still define a somewhat generalized protocol (varying numbers
>of parameters are still a problem) in which all of your non-generic
>classes have an arbitrary 'action' procedure.  Then when you pop an
>object off the list into an entity of type ANY, simply invoke
>'entity.action(params)'.  Eiffel's dynamic binder will do the rest.
>
>My guess is that your problem is more complicated than this, so you will
>have to elaborate some more.

Indeed. I've encountered this problem several places but the following
should illustrate the problem more clearly:

(btw, I cannot distribute code I write for my employer over the net so I
hope a verbal description will suffice.)

The problem domain is a relational database query compiler. In particular,
we are writing an SQL compiler so I will use SQL examples.

Consider two queries:

select a from T

This query will retrieve all the values of the field named 'a' from table T.
(I apologize if you already know all this, sorry to be a bore)

select a + b - 2.0 from T

Similarly, this query will perform the expression on each tuple and return
those values, one per tuple of T.

Somewhere in the grammar for SQL there is something like:

e-expr :: e-expr | binary-e-expr | literal | attribute

meaning that an elementary expression can be several things including such
disparate things as arbitrarialy complex sub-expressions or a simple
attribute for a table.

Attributes contain lots of information about their location within the tuple,
where their null bit is, and other similar things that are totally unrelated
to, say, a binary e-expr. The reverse is also true. This makes it difficult
to come up with a really tight generic class. The actual classes for the
base cases in such a grammar tree look extremely different.

When I'm compiling an e-expr, I have a 'incoming' list of e-exprs that I need to
deal with and take appropriate actions for each one. Unfortunately, a single
generic 'action' feature isn't good enough. For example, literal e-expr's
have a value. In the previous query there is a literal e-expr of (say) type
real with a value of 2.0. During constant folding, you need to know the
actual value so you must, somewhere, know explicitly that this is a real.
It could have been a double, boolean, integer, character, etc. and you
must know it's actual type to deal with it. While constant folding of
numeric types is fairly generic (not completely), it is quite different from
folding the other types. I cannot envision writing generic constant
folding code that wasn't extremely arcane and artificle. IMHO, that's letting
the tail wag the dog. Actually, I can't see how to do it at all but 
perhaps that's just my own inexperience. There are many similar, even more
difficult examples but they are too detailed for such a discussion.

I don't know how compelling you found this example but I hope that it, at
least, gave a clearer picture of the sorts of problems you can encounter.

Perhaps the kernel issue here to me is one of letting the programmer deal
with the paradigm they are most comfortable with. If writing generic,
typeless code results in bizzare gyrations and a lack of clarity, it is
the wrong tool for the job. I do not believe in the "one tool solves all"
theory. If it's more natural to deal with explict types, fine. Thus,
my reverse assignments. I also acknowledge this could result from my
programming background rather than a real limitation of Eiffel.

If this still isn't detailed enough, I'll try to spend some time to
come up with some real code that illustrates the problem.


tom meyer, EDS Research               | If I don't see you in the future
...uunet!tantalum!tom                 | I'll see you in the pasture

rick@tetrauk.UUCP (Rick Jones) (12/05/90)

>In article <1037@tetrauk.UUCP> I wrote:
>>
>>	a.Create (params)		a := Create A_CLASS (params)

In article <4106@tantalum.UUCP> tom@ozmium.UUCP (Tom H. Meyer) writes:
>
>I dislike this on the grounds that it would *require* that 'Create' be a
>keyword. I hope to see the day when classes can have more than one
>creation routine and this seems to disallow it.

Of course Create currently IS a keyword in effect.  The idea of multiple
creates is interesting, but if you needed them, would they not be
differentiated by their parameters?  This raises the totally separate subject
of routines overloaded on signature, something which Eiffel does not support at
all and which I've had occasion to wish for.

If this were to be added, the big question as I see it is:  should the binding
of routine selection be static or dynamic?  Eiffel's general approach is that
everything is dynamic, but my guess is that dynamic binding of routine
selection would be an horrendous overhead.  However, static binding would
probably adequately solve all the practical needs for such a facility.  This is
not something I've tried to examine in depth - perhaps it could be a topic for
a NICE technical commitee?

Going back to Creation (in the non-biblical sense), a technique I have used a
few times when object creation is non-trivial is to have an "object generator"
class.  This class offers one or more functions which return newly created
objects.  This allows different routines to create objects of the same type,
and can invisibly create descendant objects of the type actually declared.  In
this sort of circumstance, there only needs to be one generator object in the
whole system, and so it can be provided as a once function in a "definitions"
class (this is an actual use of the "global object" concept I posted recently).
I suspect this ends up being similar to Objective-C's "factory objects";
that's just a guess as I haven't actually used Objective-C.

On your topic of differentiating generic objects, I can't add much to the
discussion with Bob Weiner.  I will say that in my work the cleanest solution
has often only come after several iterations and re-thinks, and in several
cases what I've ended up doing is a lot different from what I expected to do
when I started.  Much lateral thinking is called for to get it right.

However, I think perhaps I agree with the sentiment that OO is not the optimal
paradigm for expression parsing - or else nobody's worked out the best way to
do it yet!  It's not my area of expertise so I shall refrain from commenting
further.

Keep posting!

-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan

rsw@cs.brown.EDU (Bob Weiner) (12/07/90)

In article <4144@tantalum.UUCP> tom@eds.com (Tom H. Meyer) writes:
> Indeed. I've encountered this problem several places but the following
> should illustrate the problem more clearly:
> 
> The problem domain is a relational database query compiler. In particular,
> we are writing an SQL compiler so I will use SQL examples.
> 
> Somewhere in the grammar for SQL there is something like:
> 
> e-expr :: e-expr | binary-e-expr | literal | attribute
> 
> meaning that an elementary expression can be several things including such
> disparate things as arbitrarialy complex sub-expressions or a simple
> attribute for a table.
> 

Now my question becomes whether you want to dispatch on a single type or
multiple types at certain points.  If just a single type, then I see no
problem.  Define types of COMPOSITE_E_EXPR, BINARY_E_EXPR,
LITERAL_E_EXPR, and ATTRIBUTE_E_EXPR, which then have subtypes.  In the
case of the literal, you could have INTEGER_E_EXPR, etc.  Again, each of
these could have an 'action' procedure that takes a parameter, even the
operation to be applied within the expression.

If you want to dispatch on multiple types, e.g. an optimized selection
procedure when one does an AND selection of data based upon two fields,
F1 and F2, then I would say that Eiffel does not provide a simple
mechanism to accomodate you.  C++ and CLOS make this easier through
function overloading and generic functions respectively.

> Attributes contain lots of information about their location within the tuple,
> where their null bit is, and other similar things that are totally unrelated
> to, say, a binary e-expr. The reverse is also true. This makes it difficult
> to come up with a really tight generic class. The actual classes for the
> base cases in such a grammar tree look extremely different.
> 

But that is the whole point of the 'action' feature, so you can perform
quite different actions selected by type.

> When I'm compiling an e-expr, I have a 'incoming' list of e-exprs that I need to
> deal with and take appropriate actions for each one. Unfortunately, a single
> generic 'action' feature isn't good enough. For example, literal e-expr's
> have a value. In the previous query there is a literal e-expr of (say) type
> real with a value of 2.0. During constant folding, you need to know the
> actual value so you must, somewhere, know explicitly that this is a real.

No, the dispatch mechanism, whatever it may be, must know this in order
to select the appropriate operation.  You can choose to use the dynamic
binding mechanism or produce a more limited more optimized dispatcher
yourself, though I would do that only as a last resort.

> It could have been a double, boolean, integer, character, etc. and you
> must know it's actual type to deal with it. While constant folding of
> numeric types is fairly generic (not completely), it is quite different from
> folding the other types. I cannot envision writing generic constant
> folding code that wasn't extremely arcane and artificial.

My point is not to write procedures that deal with arbitrary constants
or expressions but more simply and elegantly to use dynamic binding to
achieve specificity of operation.  Meyer deals with this issue in
Object-oriented Software Construction.

> 
> Perhaps the kernel issue here to me is one of letting the programmer deal
> with the paradigm they are most comfortable with. If writing generic,
> typeless code results in bizzare gyrations and a lack of clarity, it is
> the wrong tool for the job.

Or the design may simply be a poor one.  But I agree, let's not use
wrenches to clear our pipes.  OO design is about building the proper
tools to solve domain-specific problems.  SQL is an example of a
language that does not provide this flexibility.  If one's database
model does not fit well into a linear table format, there are still many
SQL professionals who will cram it in to one.  Now, of course, many
people are working on object-oriented databases to overcome this precise
limitation.

> If this still isn't detailed enough, I'll try to spend some time to
> come up with some real code that illustrates the problem.
> 

This would probably help.  I still feel I haven't understood the essence
of your problem.  My knowledge of SQL is quite minimal, I assure you.



--
Bob Weiner				   rsw@cs.brown.edu

tom@eds.com (Tom H. Meyer) (12/07/90)

In article <1045@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:
>>In article <1037@tetrauk.UUCP> I wrote:
>>>
>>>	a.Create (params)		a := Create A_CLASS (params)
>
>In article <4106@tantalum.UUCP> tom@ozmium.UUCP (Tom H. Meyer) writes:
>>
>>I dislike this on the grounds that it would *require* that 'Create' be a
>>keyword. I hope to see the day when classes can have more than one
>>creation routine and this seems to disallow it.
>
>Of course Create currently IS a keyword in effect.  The idea of multiple
>creates is interesting, but if you needed them, would they not be
>differentiated by their parameters?  

Not necessarily. It's easy for me to envision two different creation routines
that had identical interfaces. There's also a question of implicit information
here. I usually oppose implicit information. In this case we have, "all
creation routines are named 'Create' but you tell them apart by their
arguments." I'd rather see all the creation routines given complete liberty
with their name and, perhaps, add some additional keyword to identify this
function to the compiler as a (additional) creation routine. Perhaps

	other_create (...) is
		create_proceedure
		require
			...
		do
			...
		end;

>Going back to Creation (in the non-biblical sense), a technique I have used a
>few times when object creation is non-trivial is to have an "object generator"
>class.  This class offers one or more functions which return newly created
>objects.  This allows different routines to create objects of the same type,
>and can invisibly create descendant objects of the type actually declared.  In
>this sort of circumstance, there only needs to be one generator object in the
>whole system, and so it can be provided as a once function in a "definitions"
>class (this is an actual use of the "global object" concept I posted recently).
>I suspect this ends up being similar to Objective-C's "factory objects";
>that's just a guess as I haven't actually used Objective-C.

Sure. That's actually a better solution than what I had in mind. Since it's
fairly straight forward to get the functionality, perhaps it's better to not
clutter up the language with the additional features.

>On your topic of differentiating generic objects, I can't add much to the
>discussion with Bob Weiner.  I will say that in my work the cleanest solution
>has often only come after several iterations and re-thinks, and in several
>cases what I've ended up doing is a lot different from what I expected to do
>when I started.  Much lateral thinking is called for to get it right.

I certainly can see that. OOP is rather different is style and feel to the
other paradigms and it takes getting used to. There is a raging debate right
now about how to do object oriented design, capture the objects, etc.

tom meyer, EDS Research               | If I don't see you in the future
...uunet!tantalum!tom                 | I'll see you in the pasture