[TADS 2] OO Library Design Questions From a Newbie

24 views
Skip to first unread message

Nikos Chantziaras

unread,
Jun 27, 2002, 6:30:42 PM6/27/02
to
Hi there! I'm a C++ programmer who decided to give TADS a
try.

My first attempt as an IF-author failed horribly. I tried
to implement a text adventure engine by myself, not because
I wanted to, but simply because I was not connected to the
net and thus didn't know there are freeware systems for
writing IF (this is Greece, the Land Of The Dead Internet
Connections ;). My parser was a simple verb-noun system
(like the old Scott Adams games). Not too long ago, an
Internet Cafe opened its doors near my house...

Soon I discovered Inform, TADS, and the rest (and this
strange thing called "Usenet"). At first I tried Inform,
but turned to TADS since it's quite similar to C (Hugo is
absolutely not my thing. Can you believe it? There are no
semicolons at the end of a statement!)

TADS claims to be object oriented. Its library though is a
case for the Code Police. Consider this fragment (from
adv.t):

class obstacle: object
isobstacle = true
;

The obstacle class (and many, many others) uses a property
to identify itself as an obstacle. Since my brain is
heavily C++ oriented, this approach is a no-no for me. If
I create an object of this class:

boulder: obstacle
...
;

then I ask myself the question: "Why is boulder an
obstacle?" Now there are two answers: 1) because its class
is "obstacle", 2) because it has a property which says "I'm
an obstacle". I think you get the point. An OO library
should not try to distinguish its classes. There's no
reason to. They are unique; if an object is derived from
"obstacle", then it is an obstacle. The extra property is
not needed, and in fact it's against good OO design.

I'm not saying that TADS' default library is useless, but I
would like a better, truly object oriented, alternative. I
checked Whizzard's wadv.t, Pianosa, Alt, and WorldClass.
All of them use the same approach as adv.t. I abandoned
all hope, since if "you don't find it in the IF-Archive,
you won't find it at all". Any suggestions? (Anything
that looks like "Write your own, you lazy bone!" goes to
/dev/null :)

-- Niko

James Mitchelhill

unread,
Jun 27, 2002, 7:40:26 PM6/27/02
to
In article <8a475f2a.02062...@posting.google.com>,
rea...@hotmail.com says...


> TADS claims to be object oriented. Its library though is a
> case for the Code Police. Consider this fragment (from
> adv.t):
>
> class obstacle: object
> isobstacle = true
> ;
>
> The obstacle class (and many, many others) uses a property
> to identify itself as an obstacle. Since my brain is
> heavily C++ oriented, this approach is a no-no for me. If
> I'm not saying that TADS' default library is useless, but I
> would like a better, truly object oriented, alternative. I
> checked Whizzard's wadv.t, Pianosa, Alt, and WorldClass.
> All of them use the same approach as adv.t. I abandoned
> all hope, since if "you don't find it in the IF-Archive,
> you won't find it at all". Any suggestions?

The TADS library seems to suffer from being revised a lot, additions to
it often taking advantage of features not available in previous versions
of TADS. Fortunately, in your own code, in most cases, you're free to
choose what style of programming to use. For example, in testing if a
certain object is an obstacle you can use:

if (self.isobstacle){
}

or

if (isclass(self, obstacle)) {
}

personally, I'd like to be able to do:

if (self.isclass(obstacle) {
}

but that's just me.

I find that most of the IF programming I do is heavily customising
individual objects and my own classes. As long as you can understand
what's going on in the libraries - which is aided by the extensive
documentation included within - you generally don't need to worry about
how clean or elegant the code there is.

Besides, once you finish programming any complex IF, no matter how hard
you try to keep your code beautiful, it's almost guaranteed to look
awful. At least, mine always does.

--
James Mitchelhill
bob...@hotpop.com

Kevin Forchione

unread,
Jun 27, 2002, 9:26:44 PM6/27/02
to

> I'm not saying that TADS' default library is useless,

To do so would be pointless, given the number of games that have been
developed using it.

>but I
> would like a better, truly object oriented, alternative. I
> checked Whizzard's wadv.t, Pianosa, Alt, and WorldClass.
> All of them use the same approach as adv.t.

If by "same approach" you mean that they use properties such as the one you
point out in your post:

class obstacle
isobstacle
;

then, yes, you're right. Gasp! I never noticed that before. I wonder how we
all overlooked it before? :)

>I abandoned
> all hope, since if "you don't find it in the IF-Archive,
> you won't find it at all". Any suggestions?

Hmmm. Take a look at TADS 3. It's a bit closer to your expectations. It's
also a lot easier to write your own replacement in TADS 3 if you want - the
language is much more powerful than TADS 2.

--Kevin


Dan Schmidt

unread,
Jun 27, 2002, 10:05:32 PM6/27/02
to
rea...@hotmail.com (Nikos Chantziaras) writes:

| If I create an object of this class:
|
| boulder: obstacle
| ...
| ;
|
| then I ask myself the question: "Why is boulder an
| obstacle?" Now there are two answers: 1) because its class
| is "obstacle", 2) because it has a property which says "I'm
| an obstacle". I think you get the point. An OO library
| should not try to distinguish its classes. There's no
| reason to. They are unique; if an object is derived from
| "obstacle", then it is an obstacle. The extra property is
| not needed, and in fact it's against good OO design.

Well, if you're going to be really OO, you shouldn't be explicitly
checking whether it's an obstacle anyway; you should be using
polymorphic methods that the obstacle class overrides.

Also, if this amount of non-OO-ness disturbs you, make sure to stay
far away from Inform.

Dan

--
http://www.dfan.org

Nikos Chantziaras

unread,
Jun 30, 2002, 12:32:53 PM6/30/02
to
James Mitchelhill <bob...@hotpop.com> wrote in message news:<MPG.1785b2c4ce0786e989680@localhost>...

> Personally, I'd like to be able to do:


>
> if (self.isclass(obstacle) {
> }
>
> but that's just me.

Hmm, I like this self.isclass() thing. If the TADS
build-in type "object" would be modifiable, I could simply
do something like this:

modify object
isclass( cls ) =
{
return isclass(self, cls);
}
;

Of course it doesn't work. First, "object" is a keyword
and can't be modified. Second, isclass() is a function
name; it can't be used as a property. The best I came up
with is:

_object: object
is( cls ) =
{
return isclass(self, cls);
}
;

#define object _object
#include <adv.t>
...etc...

It's a rude hack, I know ("macros are evil"), but it seems
to work:

boulder: fixeditem, obstacle
...
;

foo: function( bar )
{
if (bar.is(obstacle)) {
...
} else {
...
}
}

> Besides, once you finish programming any complex IF, no
> matter how hard you try to keep your code beautiful, it's
> almost guaranteed to look awful. At least, mine always
> does.

Well, we all try to make our code beautiful. And yes, my
own also looks awful ;)

-- Niko

Nikos Chantziaras

unread,
Jun 30, 2002, 12:37:01 PM6/30/02
to
"Kevin Forchione" <ke...@lysseus.com> wrote in message news:<oXOS8.883$d46.14...@newssvr13.news.prodigy.com>...

> Hmmm. Take a look at TADS 3. It's a bit closer to your
> expectations. It's also a lot easier to write your own
> replacement in TADS 3 if you want - the language is much
> more powerful than TADS 2.

TADS 3 appears indeed to be very powerful. But it's still
experimental. When the first "v1.0-ready" version comes
out, I'll not bother using TADS 2 anymore (I hope everyone
else will make the switch too, since I'm not willing to use
a "died at birth" language). I think that with TADS 3 the
IF-Community will have a powerful tool in its hands.

-- Niko

Nikos Chantziaras

unread,
Jun 30, 2002, 12:38:48 PM6/30/02
to
Dan Schmidt <df...@dfan.org> wrote in message news:<u4rfom...@dfan.thecia.net>...

> Well, if you're going to be really OO, you shouldn't be
> explicitly checking whether it's an obstacle anyway; you
> should be using polymorphic methods that the obstacle
> class overrides.

Sometimes this isn't possible. When you have a bunch of
objects, in a bag for example, and you want to do something
with the bag that affects it's contents (a spell maybe),
then you have to know each object's class, because TADS
doesn't support multiple indirect objects. Of course you
can do this using properties, like an "isimmune" property
which is true when the object can't be affected by magic at
all. But since TADS supports multiple inheritance (a great
feature to have), this is what I'm trying to avoid.

Or consider a "cast death" spell which kills anything
animate around the player character (not "cast death at
everything"; we can't have multiple indirect objects).
Instead of searching all objects in the current location
for

obj.isanimate == true

in the spell's doCast-method, I would like to simply find
out if they are animate. I can do this with isclass(), but
the "obj.isclass(animate)" approach seems better to me.
This reminds me of the Object Pascal-way of doing this. It
would be great if TADS had the "is" keyword:

if (obj is animate)

Also, defining properties for this task can result in
identifier-collisions.

I think the reason why things are as they are, is the fact
that you can't change the class of an object at runtime.
You can fake this by setting it's property to nil. An
object of class "obstacle" is not an obstacle anymore if
its "isobstacle" property is nil (but it's still of class
"obstacle", which can make things very complicated). I may
be wrong with that though. Note that being able to change
an object's class at runtime would fit perfectly in TADS',
since it's type free. The only thing that we had to do in
this case is defining classes which are abstract enough;
not just:

class thing: object

but:

class thing: describable, takeable, etc.

Since I'm starting to say things that sound like "I don't
like TADS, change it!", I'll just shut up now and go write
some code. =8<> <-- (By the way, that's a chicken.)

-- Niko

Kevin Forchione

unread,
Jun 30, 2002, 4:23:01 PM6/30/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote in message
news:8a475f2a.02063...@posting.google.com...

> James Mitchelhill <bob...@hotpop.com> wrote in message
news:<MPG.1785b2c4ce0786e989680@localhost>...
>
> > Personally, I'd like to be able to do:
> >
> > if (self.isclass(obstacle) {
> > }
> >
> > but that's just me.
>
> Hmm, I like this self.isclass() thing. If the TADS
> build-in type "object" would be modifiable, I could simply
> do something like this:
>
> modify object
> isclass( cls ) =
> {
> return isclass(self, cls);
> }
> ;
>
> Of course it doesn't work. First, "object" is a keyword
> and can't be modified. Second, isclass() is a function
> name; it can't be used as a property. The best I came up
> with is:
>
> _object: object
> is( cls ) =
> {
> return isclass(self, cls);
> }
> ;

Try using

_object: object
isClass( cls ) =
{
return isclass(self, cls);
}
;

> #define object _object
> #include <adv.t>
> ...etc...
>
> It's a rude hack, I know ("macros are evil"), but it seems
> to work:

Macros aren't particularly 'evil' they simply translate one form of syntax
into another. Languages such as C and C++ provide this feature, and many
languages use translation for facilitating integration with database
services or CICS.

TADS 3 makes extensive use of macros - from defining grammar to defining
verification and action methods.

> boulder: fixeditem, obstacle
> ...
> ;
>
> foo: function( bar )
> {
> if (bar.is(obstacle)) {
> ...
> } else {
> ...
> }
> }

By the way, TADS 3 has an isClass() property, which returns true/nil
depending on whether the object was defined with the 'class' keyword. The
equivalent to the above in TADS 3 is the ofKind() property, which would be
defined in TADS 2 as:

_object: object
ofKind( cls ) =
{
return ((self == cls) || isclass(self, cls));
}
;

One of the things you ought to keep in mind with the TADS object model, as
stated by the TADS 2 Author's manual, chapter 2:
Users of other languages that have object-oriented features, such as C++
or Smalltalk, will find that TADS has a slightly different approach to
object-oriented programming. In particular, TADS makes much less of a
distinction between classes and objects than other languages.

Particularly in C++, a class is a template that specifies the data types
stored by members of the class, but only an object actually stores values
for the data. (This is analogous to the distinction between a structure and
instances of the structure in a language such as C or Pascal: the structure
defines the layout of data, but only an instance of the structure actually
contains any data.) The inheritance structure pertains to the classes; an
object is an instance of a particular class, so only methods and "slots" for
data can be inherited - values for data items are not themselves inherited.

In TADS, there is no distinction between classes and instances of classes,
in that a class is also an object. Hence, data values as well as methods can
be inherited from a parent object. In TADS, the only distinction between a
class and a normal object is that a class object is ignored by the player
command parser.

This different approach leads to a different style of programming. A TADS
program consists mostly of definitions of objects (instances of classes).
All of the instances are specifically defined, with values for properties,
in the game program. In contrast, a C++ or Smalltalk program usually defines
mostly classes, and creates instances at run-time.

Comparison to Java and C++ are not always applicable with TADS. In TADS, for
instance, you can dynamically add a property to an object, and this property
will be added to every object that inherits from the object.

This object model is also adopted by TADS 3. In TADS 3 there are only a few
distinctions between object's defined with the 'class' keyword and those
that are not...

* the isClass() property is what distinguishes an object that has been
defined with the 'class' keyword from one that has not.
* the firstObj() / nextObj() takes arguments that allow you to loop over
objects defined with the 'class' keyword or exclude them.
* the 'new' operator allows an author to create an instance of an object
that has been defined with the 'class' keyword, while createInstance()
allows an author to create an instance of an object that has not been
defined with the 'class' keyword.

--Kevin

David Picton

unread,
Jul 1, 2002, 2:13:21 PM7/1/02
to
Dan Schmidt <df...@dfan.org> wrote in message news:<u4rfom...@dfan.thecia.net>...
> rea...@hotmail.com (Nikos Chantziaras) writes:
>
> | If I create an object of this class:
> |
> | boulder: obstacle
> | ...
> | ;
> |
> | then I ask myself the question: "Why is boulder an
> | obstacle?" Now there are two answers: 1) because its class
> | is "obstacle", 2) because it has a property which says "I'm
> | an obstacle". I think you get the point. An OO library
> | should not try to distinguish its classes. There's no
> | reason to. They are unique; if an object is derived from
> | "obstacle", then it is an obstacle. The extra property is
> | not needed, and in fact it's against good OO design.
>
> Well, if you're going to be really OO, you shouldn't be explicitly
> checking whether it's an obstacle anyway; you should be using
> polymorphic methods that the obstacle class overrides.

This reminds me of the old arguments about the use of "goto". Purists
would ban the use of "goto" altogether, but more pragmatic programmers
find that it is helpful under some circumstances, e.g. when handling
exception conditions.

Similarly in TADS, it would be possible to adopt a purist, pure OO
approach in which classes are never explicitly looked at. For
example, the travelTo method could do something like this:

newroom := (room.destination);
if(newroom) actor.moveInto(newroom);

Objects of class "obstacle" would define the destination method as at
present,
whereas the "room" class would define "destination = {return self;}".

This approach certainly has advantages; in particular, the travelTo
method would be simpler. However, I would argue that it also has
disadvantages; the travelTo code would no longer be self-contained, so
that a game author who wanted to understand what was going on would
need to consult the code for the room and obstacle classes, as well as
the travelTo method for the actor.

Mike Roberts

unread,
Jul 1, 2002, 2:41:32 PM7/1/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote:
> class obstacle: object
> isobstacle = true
> ;
>
> If I create an object of this class:
> boulder: obstacle // etc

>
> then I ask myself the question: "Why is boulder an
> obstacle?" Now there are two answers: 1) because
> its class is "obstacle", 2) because it has a property
> which says "I'm an obstacle".

The tads 2 library does use the "isfoo" property approach entirely too much,
I'll cheerfully concede.

By way of half-hearted defense of this programming style, note that it
actually has some OO advantages over your preferred method of checking the
class of an object to see if it's a "foo". When code in an OO system checks
the class of an object, it is encoding assumptions about the class hierarchy
and the behavior of certain classes into code outside those classes, which
runs counter to the OO goal of encapsulation. By using a property that
indicates that something *behaves* a certain way, you're separating
knowledge of the class hierarchy from, essentially, the programmable
interfaces of the classes, and it facilitates adding a particular behavior
to several unrelated classes.

I can only mount a half-hearted defense, though, because I've come to think
neither approach - checking the class vs checking a property indicating a
particular behavior - is ideal, and you'll find the tads 3 library uses a
rather different style in most cases. The approach that I've come to prefer
is to treat any situation where you feel like you need to know the class of
an object as a warning flag, and think carefully about what you really want
to know about the object or what you're really trying to do with it;
ideally, you can create an interface that allows you to encapsulate whatever
is special about the class you want to identify in that class itself rather
than in the calling code. Obstacles actually provide a pretty good example
of this, if you want to contrast how they're handled in the tads 3 library
vs the tads 2 library.

--Mike
mjr underscore at hotmail dot com

Nikos Chantziaras

unread,
Jul 2, 2002, 6:03:03 PM7/2/02
to
"Kevin Forchione" <ke...@lysseus.com> wrote in message news:<FMJT8.417$j%6.414...@newssvr13.news.prodigy.com>...

> Macros aren't particularly 'evil' they simply translate
> one form of syntax into another. Languages such as C and
> C++ provide this feature, and many languages use
> translation for facilitating integration with database
> services or CICS.

I have learned to avoid macros as much as possible. Most
difficult to trace bugs in my programs were related to
macros. Macros *are* evil because they allow you to modify
the language itself. But they are also very powerful. A
complex macro is like an F117 with all its weapons pointing
at you feet; one wrong step -- bang, you're dead.

> TADS 3 makes extensive use of macros - from defining
> grammar to defining verification and action methods.

There is nothing wrong with using macros in a library as
long as their use is transparent to the user (the standard
C++ library headers consist mainly of macros; but the
programmer doesn't know this, and, most importantly,
doesn't *need* to know).

> Comparison to Java and C++ are not always applicable with
> TADS. In TADS, for instance, you can dynamically add a
> property to an object, and this property will be added to
> every object that inherits from the object.

Nice feature that.

-- Niko

Nikos Chantziaras

unread,
Jul 3, 2002, 5:19:02 PM7/3/02
to
"Mike Roberts" <mjr-S...@hotmail.com> wrote in message news:<8n1U8.8$wz3...@news.oracle.com>...
> "Nikos Chantziaras" <rea...@hotmail.com> wrote:
> > [...]

> > If I create an object of this class:
> > boulder: obstacle // etc
> >
> > then I ask myself the question: "Why is boulder an
> > obstacle?" Now there are two answers: 1) because
> > its class is "obstacle", 2) because it has a property
> > which says "I'm an obstacle".
>
> [...] By way of half-hearted defense of this programming style,

> note that it actually has some OO advantages over your preferred
> method of checking the class of an object to see if it's a "foo".
> When code in an OO system checks the class of an object, it is
> encoding assumptions about the class hierarchy and the behavior of
> certain classes into code outside those classes, which runs counter
> to the OO goal of encapsulation.

Since I'm still a TADS-newbie, my code looks like C++ that wants to
be TADS (or vice versa). I still can't figure out how to separate
user-code from library implementation details in an elegant way.
TADS is a type-free language and I end up checking almost everything
at runtime:

someGuy: Actor
actorAction(v, d, p, i) =
{
if (v == inspectVerb) {
if (isclass(d, book) { // Or: "if (d.isbook)"
"<q>A book. Let's read it!</q> ";
exit;
}
}
pass actorAction;
}
;

Maybe I'm wrong, but it seems that there is no other way. Of course
I could do:

...
if (d == magicBook or smallBook or yellowBook or [etc])
...

but this would be ridiculous. Another solution is to implement the
book class in such a way that it knows how to respond when the actor
examines it. But this would be an obvious mistake (a book doesn't
have to know anything about actors; the actors have to know how to
examine a book). I think being type-free is both a gift and curse.
(Another possibility is that my approach is completely wrong; how do
other authors deal with situations like this?)

> By using a property that indicates that something *behaves* a
> certain way, you're separating knowledge of the class hierarchy
> from, essentially, the programmable interfaces of the classes, and
> it facilitates adding a particular behavior to several unrelated
> classes.

Correct me if I'm wrong, but why should several classes provide the
same behavior if they are unrelated? A code example of what exactly
is meant here would be great.

> [...] The approach that I've come to prefer is to treat any


> situation where you feel like you need to know the class of an
> object as a warning flag, and think carefully about what you really
> want to know about the object or what you're really trying to do
> with it;

I agree. Trying to find out an object's class at runtime is an
indication that you're doing something wrong. It's just harder to
avoid it in a type-free language like TADS.

> ideally, you can create an interface that allows you to
> encapsulate whatever is special about the class you want to
> identify in that class itself rather than in the calling code.

> [...]

Finally, I get the strange feeling that the reason why all the
existing TADS libraries make heavy use of the "isfoo" property, is
that people are used to it. (As far as I know, a call to isclass()
is much faster than searching through the inheritance tree for a
particular property value; so states the manual.)

-- Niko

Nikos Chantziaras

unread,
Jul 3, 2002, 5:21:39 PM7/3/02
to
pict...@bigmailbox.net (David Picton) wrote in message news:<79d462f4.0207...@posting.google.com>...

> Dan Schmidt <df...@dfan.org> wrote in message
> news:<u4rfom...@dfan.thecia.net>...
>
> > Well, if you're going to be really OO, you shouldn't be
> > explicitly checking whether it's an obstacle anyway; you should
> > be using polymorphic methods that the obstacle class overrides.
>
> This reminds me of the old arguments about the use of "goto".
> Purists would ban the use of "goto" altogether, but more pragmatic
> programmers find that it is helpful under some circumstances, e.g.
> when handling exception conditions.

What would parser-generation be without goto? Lex and yacc make
heavy use of it. But that's off topic.

> Similarly in TADS, it would be possible to adopt a purist, pure OO
> approach in which classes are never explicitly looked at. For
> example, the travelTo method could do something like this:
>
> newroom := (room.destination);
> if(newroom) actor.moveInto(newroom);
>
> Objects of class "obstacle" would define the destination method as
> at present, whereas the "room" class would define
> "destination = {return self;}".
>
> This approach certainly has advantages; in particular, the travelTo
> method would be simpler. However, I would argue that it also has
> disadvantages; the travelTo code would no longer be self-contained,
> so that a game author who wanted to understand what was going on
> would need to consult the code for the room and obstacle classes,
> as well as the travelTo method for the actor.

In a perfect world, the author should not even need the library
sources. Simply calling travelTo() should be enough. "As long as it
does what I need, who cares how it works?" The only language I
know of that does this, is NWY.

-- Niko

L. Ross Raszewski

unread,
Jul 3, 2002, 6:31:23 PM7/3/02
to
On 3 Jul 2002 14:21:39 -0700, Nikos Chantziaras <rea...@hotmail.com> wrote:
>pict...@bigmailbox.net (David Picton) wrote in message
news:<79d462f4.0207...@posting.google.com>...
>What would parser-generation be without goto? Lex and yacc make
>heavy use of it. But that's off topic.

You can write parsers in ML and smalltalk. No goto in either of them.

Mike Roberts

unread,
Jul 3, 2002, 6:37:01 PM7/3/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote:
> mjr:

> > By using a property that indicates that something *behaves*
> > a certain way, you're separating knowledge of the class
> > hierarchy from, essentially, the programmable interfaces of
> > the classes, and it facilitates adding a particular behavior to
> > several unrelated classes.
>
> Correct me if I'm wrong, but why should several classes provide
> the same behavior if they are unrelated? A code example of
> what exactly is meant here would be great.

A canonical abstract programming example is an iterator interface, where you
have a bunch of different classes that contain collections of objects stored
in different ways, and you want a common way to iterate through the
collections regardless of class. One class uses an array; another uses a
linked list; another uses a hash table. Rather than requiring that all of
the collection classes derive from a common base class that provides the
iteration methods, you could define an iteration interface that any class
can implement, regardless of its derivation. Java does something along
these lines with its Collection interface.

Now, you might ask: why not just use a common base class? The answer is
that it's not always desirable to do this, because you might want to define
the same iterator interface on new classes even when they share nothing else
with any other collection types. You might, for example, want to add the
iterator interface to a String class, to iterate through its characters;
without the interface mechanism, you'd have to redefine the String class to
derive from a base class in common with the other collection types, which
you obviously couldn't do if the class were system-defined or from a third
party library. And even if you could redefine String's base class, you
might not want to, because doing so could add a bunch of cruft from the
collection base class that you'd have to override to do nothing, which is in
itself poor OO design.

For a more IF-grounded example, the tads 3 library has a "travel connector"
interface that's used for any object referenced in a direction link from a
room. The interface has a bunch of methods that carry out the operations
involved in travel; the travel mechanisms can call these methods because
they know any object used as a direction link will provide them. This is
great because it means that we can plug in doors, rooms, barriers, stairs,
and anything else that implements the travel connector methods - and the
travel mechanism never has to switch on the class of any of these objects,
because all it cares about is that it can call the connector methods. This
class independence means that we can add new types of travel connectors
without ever having to modify any of the travel mechanism code itself.

> I agree. Trying to find out an object's class at runtime
> is an indication that you're doing something wrong. It's
> just harder to avoid it in a type-free language like TADS.

TADS isn't a type-free language; it's a late-bound, or run-time-typed,
language. If it were type-free, you couldn't check the class at run-time.

But that aside, this doesn't make it any harder to avoid checking types; it
just makes it so easy to check types that it's often the easiest and most
convenient way to do something. Suppose you were working with C++, and you
wanted to write your actorAction example:

>actorAction(v, d, p, i) =
>{
> if (v == inspectVerb) {

> if (isclass(d, book) // etc

In C++, we'd write the method definition something like this:

void ActorBob::actorAction(DeepVerb *v, Item *d, Prep *p, Item *i)
{
if (v == InspectVerb::theSingletonInstance && ???) // etc
}

Let me ask this: how does C++'s compile-time typing make it easier to avoid
checking the type of 'd' in this example? In other words, how are you going
to redesign this to avoid some kind of RTTI query where I've put the "???"?
Whatever it is you end up with, why can't you do that same thing with TADS?

Nikos Chantziaras

unread,
Jul 5, 2002, 4:40:50 PM7/5/02
to
"Mike Roberts" <mjr-S...@hotmail.com> wrote in message
news:K%KU8.13$4x6...@news.oracle.com...

> A canonical abstract programming example is an iterator interface,
> where you have a bunch of different classes that contain
> collections of objects stored in different ways, and you want a
> common way to iterate through the collections regardless of class.
> One class uses an array; another uses a linked list; another uses a
> hash table. Rather than requiring that all of the collection
> classes derive from a common base class that provides the iteration
> methods, you could define an iteration interface that any class can
> implement, regardless of its derivation. [...]
<<snip stuff I don't disagree with>>

The funny thing is that I use iterators like hell, but I never
*really* thought about the theory behind them!

> TADS isn't a type-free language; it's a late-bound, or
> run-time-typed, language. If it were type-free, you couldn't check

> the class at run-time. [...]

I said "type-free" but what I meant was actually "late-bound".
Sorry.

> > actorAction(v, d, p, i) =
> > {
> > if (v == inspectVerb) {
> > if (isclass(d, book) // etc
>
> In C++, we'd write the method definition something like this:
>
> void ActorBob::actorAction(DeepVerb *v, Item *d, Prep *p, Item *i)
> {
> if (v == InspectVerb::theSingletonInstance && ???) // etc
> }
>
> Let me ask this: how does C++'s compile-time typing make it easier
> to avoid checking the type of 'd' in this example? In other words,
> how are you going to redesign this to avoid some kind of RTTI query
> where I've put the "???"? Whatever it is you end up with, why can't
> you do that same thing with TADS?

In C++, I would provide a general method like the above that handles
any type of item, as well as some more specialized ones for certain
kinds of items, making heavy use of function overloading (this is a
bit simplified):

// This is the most general routine.
void ActorBob::actorAction( DeepVerb &v,Thing &d,Prep &p,Thing &i )
{ ... }

// And now the special cases.
void ActorBob::actorAction( inspectVerb &v, Book &d )
{
// Code that handles inspecting books.
}

The downside is that it's a hell of a job to implement a library that
makes the above code work as expected (because the library doesn't
know anything about the Book class). The library must solve this
problem by using macros or, better, templates or even a combination
of both. And it doesn't matter how complex the library's
implementation is as long as its interface is easy to use.

It's obvious that we can't do this in TADS, since it doesn't support
function overloading and templates (because it's a late-bound
language).

Nikos Chantziaras

unread,
Jul 5, 2002, 4:44:11 PM7/5/02
to
"L. Ross Raszewski" <lrasz...@loyola.edu> wrote in message
news:%WKU8.3350$jr3....@nwrddc02.gnilink.net...

> You can write parsers in ML and smalltalk. No goto in either of them.

I love my yacc and lex
As well as my bison and flex
I love both C and C++
Cuz it's great for all of us
I love my goto
I hate my goto
But I don't have to use it
That's what lex and yacc are for
Now it's time to end the poem
Cuz it's poor and doesn't rhyme

:-)

-- Niko

Mike Roberts

unread,
Jul 5, 2002, 5:47:34 PM7/5/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote:
> mjr:
> > Let me ask this: how does C++'s compile-time typing
> > make it easier to avoid checking the type of 'd' in this
> > example?
>
> In C++, I would provide a general method like the above
> that handles any type of item, as well as some more specialized
> ones for certain kinds of items, making heavy use of function
> overloading:

>
> // This is the most general routine.
> void ActorBob::actorAction( DeepVerb &v,Thing &d,
> Prep &p,Thing &i )
>
> // And now the special cases.
> void ActorBob::actorAction( inspectVerb &v, Book &d )
>
> The downside is that it's a hell of a job to implement a library
> that makes the above code work as expected (because the
> library doesn't know anything about the Book class). The
> library must solve this problem by using macros or, better,
> templates or even a combination of both.

Okay, this is what I thought you had in mind. Let me point out a couple of
things about it.

First, I don't think there is in fact any way you could do what you propose
by using templates or macros or any other compile-time binding. C++
function overloading is bound at compile-time - there's no such thing as
"virtual overloading" that would let you choose a function to call based on
the run-time type signature of the actuals. Function overloading picks the
function to call based on the *compile-time* signature. As for templates,
these are bound to specific types at compile time, so I don't think they'll
be of any help. Macros are a red herring, in that they just get turned
into C++ that you could have written without macros in the first place. The
only way you could accomplish this kind dynamic type-based function
selection in C++, I believe, is to use use RTTI to check types at run-time
and then switch on the types - which is exactly what you're trying to avoid
because it's bad OO design.

Don't take my word for it. It sounds like you know C++, so take a shot at
putting together the basic mechanism for this and see if you can come up
with something compile-time-bound.

Second, stepping back a bit from these implementation details, how is your
proposal really any different, from an OO design perspective, from
explicitly switching on the type? It seems to me that the difference is
pure syntax: rather than writing an explicit type switch in your actorAction
implementation, you're counting on the library to make exactly the same type
check and call different routines depending on the types. In other words,
the "bad" way was this:

[game]
actorAction { if (type1) do thing1; else do thing2; }

and the "good" way is this:

[library]
if (type1) call actorAction1; else call actorAction2; }
[game]
actorAction1 { do thing1; }
actorAction2 { do thing2; }

They're exactly the same logic; the second doesn't adhere any more closely
to OO design principles than the first except in some superficial, syntactic
way. In particular, code pertaining to the type "Book" is still
de-encapsulated into the actorAction routines.

> It's obvious that we can't do this in TADS, since it doesn't
> support function overloading and templates (because it's a late-
> bound language).

In fact, you could quite well do the same thing in tads. C++ function
overloading looks like something special and magical, but it's not; all it's
really doing is applying a *naming convention* based on the type signature.
You could do the exact same thing in tads merely by using an explicit naming
convention:

actorAction(v, d, p, i)
actorActionInspectBook(v, d)
actorActionPutIn(v, d, i)
actorActionOpen(v, d)
actorActionOpenBook(v, d)

This would be almost easy using tads 3 reflection (which lets you look up
strings in the compiler's symbol table dynamically at run time). Without
reflection, it's quite a lot harder - but as you said,

>And it doesn't matter how complex the library's
>implementation is as long as its interface is easy to use.

And furthermore, without reflection, you'd have to resort to the same kind
of RTTI tricks that would be necessary in C++, so whatever implementation
you come up with in C++ should be translatable in similar terms to tads.

Nikos Chantziaras

unread,
Jul 6, 2002, 5:13:55 PM7/6/02
to
"Mike Roberts" <mjr-S...@hotmail.com> wrote in message
news:atoV8.24$n62...@news.oracle.com...

> > // This is the most general routine.
> > void ActorBob::actorAction( DeepVerb &v,Thing &d,
> > Prep &p,Thing &i )
> >
> > // And now the special cases.
> > void ActorBob::actorAction( inspectVerb &v, Book &d )
>
> First, I don't think there is in fact any way you could do what you
> propose by using templates or macros or any other compile-time
> binding. [...]

With some evil tricks, yes.

/****************
* LIBRARY CODE *
****************/
class Thing {};
class Container: public Thing {};

class Actor: public Thing
{
public:
virtual void action( Thing* );
virtual void action( Container* );
};

template <class A, class T>
void actionHandler( A* act, T* obj )
{
/* Before calling the action() method, some sanity checks must
be performed to see if the arguments make sense. This could be
done by checking for the existence of methods that only the
interfaces of Thing and Actor provide, or other evil
compile-time tricks. The solution to this problem should not
affect the user in any way. This is the location where all the
ugliness from other code is shifted to. */

act->action(obj);
}

/*************
* USER CODE *
*************/
class Book: public Thing {};

class ActorBob: public Actor
{
public:
/* Note that the action() methods are virtual, but, as you said,
there is no such thing as "virtual overloading"; we need all
the original action() methods because what we try to do here
is allowed only for the return value of a method, not for its
arguments. */
Actor::action; // A macro could automate this.

// This one doesn't override anything. It's just a new method.
virtual void action( Book* );
};

It could work. I must admit though that it's not worth the effort.
(For simplicity, I left out handling of prepositions and verbs.)

> [...] Macros are a red herring, in that they just get turned into


> C++ that you could have written without macros in the first place.

Macros are great for automating non-trivial tasks (like the one we
are talking about right now). And they allow the library to gain
information about user code (identifiers for example).

> Second, stepping back a bit from these implementation details, how
> is your proposal really any different, from an OO design
> perspective, from explicitly switching on the type? It seems to me
> that the difference is pure syntax: rather than writing an explicit
> type switch in your actorAction implementation, you're counting on
> the library to make exactly the same type check and call different
> routines depending on the types.

Yes, basically it's the same thing. The only advantage is that the
more errors we catch at compile time, the more BugFree(TM) our game
becomes.

> They're exactly the same logic; the second doesn't adhere any more
> closely to OO design principles than the first except in some
> superficial, syntactic way. In particular, code pertaining to the
> type "Book" is still de-encapsulated into the actorAction routines.

Well, I tried to find an elegant solution but failed. Any ideas?

> In fact, you could quite well do the same thing [function
> overloading] in tads. C++ function overloading looks like something


> special and magical, but it's not; all it's really doing is
> applying a *naming convention* based on the type signature. You
> could do the exact same thing in tads merely by using an explicit

> naming convention [...]

Function overloading allows you to call a function which name you
don't even know. You simply provide a "could be anything" name and
the compiler does the rest for you. See? Magic! ;-)

> And furthermore, without reflection, you'd have to resort to the
> same kind of RTTI tricks that would be necessary in C++, so
> whatever implementation you come up with in C++ should be
> translatable in similar terms to tads.

Depends on how you see it. I *could* write an OO program in C or
plain Pascal ("in similar terms"). But it would be language "abuse".
Tads is a language designed for developing IF (and it does a great
job at this). It would be wrong to use it as "the C++ for IF", since
C++ is designed for developing, well, everything.

Finally, I think we lost the point. The question is not how to do it
in C++, but in Tads.

-- Niko

Mike Roberts

unread,
Jul 8, 2002, 2:28:30 PM7/8/02
to
I might be going a little off topic, but there are a couple of interesting
OO design points here, so I'm keen to pursue a couple of details a little
further.

Nikos Chantziaras (rea...@hotmail.com) wrote:
> [how to use C++ templates to select an overloaded
> function by type signature]


>
>template <class A, class T>
>void actionHandler( A* act, T* obj )
>{

> act->action(obj);
>}

This template is the core of your scheme, and this is also at the heart of
why I continue to claim that compile-time binding won't work the way you
want it to. Let me explain. You're seeing this template being called from
some code like so:

Actor *a = <some Actor instance>
Book *b = <some Book instance>
// now I want to call the actionHandler method
// that's specialized for (Actor, Book) - I can
// use the template to do this!
actionHandler(a, b);

So far, so good; that'll do what you want. There's only one problem: the
code above isn't the kind of code that we need to "virtualize" with respect
to the type signature. Here's the code that we *really* want to get working
with your signature-based selector:

Actor *a = <some Actor>
Thing *t = <some Thing, happens to be a Book>
actionHandler(a, t);

I think you'll agree that your template will be of no help here. As far as
the compiler knows, t is just a Thing - you're not telling compiler it's a
Book. The template you're going to invoke is the (Actor,Thing) template,
which means you're going to call your most basic action handler. It doesn't
matter that we've defined an action(Book*) handler in ActorBob - it won't
get called, even though t is actually a Book in this particular case,
because all the compiler knows is that t is a Thing. The template is bound
at compile time; at compile-time, t is a Thing, so the compiler will call
action(Thing*) from the template.

But, you say, I'm being incredibly dense: the whole point of compile-time
binding is that we *can* and *should* use the specific type in our
declarations! That's your entire point: C++ is better in this respect than
TADS precisely because we can tell C++ the type at compile-time, and good
coding practice requires us to use the specific type in the declaration, so
that the compiler can help us by enforcing strict typing. So how can I
argue that we can't tell that t is a Book at compile-time when we perfectly
well could have just typed "Book *t" instead of "Thing *t"? Why do I insist
on using these vague types in my declarations?

Think for a moment about the code that calls your action handler. Remember
the specific case we've been talking about: actorAction(). What is this
code that calls actorAction? It's the *parser*.

Okay, so what's so special about the parser that it gets to violate our
strict-typing policy? Well, think about how the parser is going to get its
reference to that Book instance in the first place. The user is going to
type a command like this:

inspect the blue book

That's going to come in as a string from stdin. The parser is going to pick
that string apart into tokens, run the tokens through some grammar rules,
figure out that "blue" is an adjective and "book" is a noun, look up those
strings in a table that associates strings with object instances, and come
up with some object instance or instances matching those strings. What
comes out of that table? *Thing* pointers. It's obvious that the
dictionary and the parser have to deal in the basic types, as far as
compile-time declarations are concerned, right? I mean, you can't very well
have lines of code in your parser with "Book *" declarations; if you did,
you'd also have to have lines of code for Actor objects, Room objects,
DarkRoom objects, OutdoorRoom, IndoorRoom, Platform, Chair, Bed, and on and
on.

Do you see my point? In order for your compile-time binding scheme to work,
the C++ code implementing the parser has to know *at compile time* the final
subclass of the objects it's dealing with. This is simply not possible - if
you hard-coded a final subclass declaration for every possible subclass into
the parser's C++ code, you'd have to add code to the parser every time you
added a new subclass. Clearly, our parser has to be coded so that when it
resolves a string into an object reference, the object reference is some
base type that can represent anything in the game that can be bound to
vocabulary words; so given one of these base object references, the parser
has to be able to call methods on it with no compile-time binding to any
more specific subclasses.

So that's the challenge we have to overcome: given this parser code...

Actor *a;
Thing *t;
actionHandler(a, t);

...find a way to make that code above call actionHandler(Actor*, Book*) when
t is actually a Book instance at run-time, and actionHandler(Actor*, Thing*)
in other cases. Do you see any way to do this with macros or templates? Do
you even see a *theoretical* way of doing this with compile-time binding?

> mjr:
>> In fact, you could quite well do [function
>> overloading] in tads. [...] You could do the


>> exact same thing in tads merely by using an
>> explicit naming convention [...]
>
>Function overloading allows you to call a function
>which name you don't even know. You simply provide
>a "could be anything" name and the compiler does
>the rest for you.

This was just my point, though. Your words: function overloading allows you
to *call* a function without knowing the (signature-qualified) name. In
other words, you just have to know the *root* name, and the convention for
turning the root plus signature into the full name. In C++, the compiler
does that name translation behind the scenes. In TADS, you could have the
*library* do the same name translation behind the scenes.

You agree that, even in C++, you have to know the full signature-qualified
name of the function you want to declare, though, right? In other words,
you're explicitly going to code actorAction(Book *) as one function, and
actorAction(Thing *) as a separate function. So on the declaration side, my
point is that we can apply the naming convention manually, to write a pair
of functions with names like actorActionBook() and actorActionThing(). We
haven't done anything that's fundamentally different than we do in C++ when
we use function overloading - we're simply applying the naming convention
explicitly to do the overloading.

On the calling side, we still make the call to the function whose name we
don't know - in other words, we don't have to know the signature-qualified
name, but just the root name and the convention. The convention is encoded
in the library, which takes the root name and the parameters, using string
manipulation to build the signature-qualified name, and then uses reflection
(i.e., run-time symbol lookup) to find the function given the string name.
The effect is the same as C++ overloading - with the added bonus that it's
all bound at run-time, so you actually can achieve the "virtual overloading"
that C++ can't do.

Nikos Chantziaras

unread,
Jul 9, 2002, 5:00:50 PM7/9/02
to
"Mike Roberts" <mjr-S...@hotmail.com> wrote in message
news:iQkW8.26$uO2...@news.oracle.com...
> [...]

> ...find a way to make that code above call actionHandler(Actor*, Book*)
when
> t is actually a Book instance at run-time, and actionHandler(Actor*,
Thing*)
> in other cases. Do you see any way to do this with macros or templates?
Do
> you even see a *theoretical* way of doing this with compile-time binding?
Okay, I'll try to implement a working system. Currently I'm doing some
research
on how other languages solve this. I looked at Object Pascal and its
concept of
"metaclasses". They allow a library to make assumptions about unknown
classes.
I'll check if something similar can be done in C++ and if it helps solving
the problem.

Hang on...

-- Niko

Mike Roberts

unread,
Jul 9, 2002, 9:35:53 PM7/9/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote:
> mjr:
>> Do you see any way to do this with macros or templates?
>> Do you even see a *theoretical* way of doing this with
>> compile-time binding?
> Okay, I'll try to implement a working system. Currently I'm
> doing some research on how other languages solve this. I
> looked at Object Pascal and its concept of "metaclasses".
> They allow a library to make assumptions about unknown
> classes.

I'm not familiar with object pascal's introspection features, but you can
certainly implement "virtual overloading" in java (or tads 3) with
reflection. That actually wasn't in question, though; the point of
contention was whether or not you could use *compile-time* bindings:

> The only advantage is that the more errors we catch at
> compile time, the more BugFree(TM) our game becomes.

--Mike

Nikos Chantziaras

unread,
Jul 10, 2002, 4:24:26 PM7/10/02
to
"Mike Roberts" <mjr-S...@hotmail.com> wrote in message
news:9bMW8.21$FA....@news.oracle.com...

> I'm not familiar with object pascal's introspection features, but you can
> certainly implement "virtual overloading" in java (or tads 3) with
> reflection. That actually wasn't in question, though; the point of
> contention was whether or not you could use *compile-time* bindings:
I'm already giving up hope; it seems it's impossible. I would love though
to
prove you wrong, since I saw some very interesting things done in C++.
Consider QT and its moc precompiler. I'll see what I can come up with.

-- Niko


Raybert

unread,
Jul 11, 2002, 5:46:09 PM7/11/02
to
Hi,

I've been following this thread and it's been pretty interesting. I find
myself disagreeing however with one of the basic premises of it. Namely,
that checking the class of an object is a bad thing in an OO sense.

Polymorphism is an OO feature whose purpose is to generalize specialized
capabilities *when it is desirable to do so*. It is NOT always desirable to
generalize and OO does not require that you always do so. Furthermore, not
all specializations are capable of being generalized (as we're seeing in this
discussion).

We have a situation in IF game programming where we want to generalize some
aspects of our game objects some of the time, but we cannot do so
(reasonably) all of the time. This is actually not all that uncommon of a
situation and it is not necessarily indicative of a bad OO design: it's just
the natural conclusion to modeling a very complex world.

In the general case, interrogating the class hierarchy defeats polymorphism.
However, this is not true in the case where we need to detect some
non-generalized specialized capability, because that capability was not
polymorphic to begin with! Furthermore, there is nothing in OO design
philosophy that dictates that we must *always* use and preserve polymorphism.
It is a tool to be used when our design calls for it. If our design
necessitates having non-polymorphic specialized capabilities within a
nominally polymorphic class hierarchy, there is nothing wrong with
temporarily defeating polymorphism to access those specialized capabilities.

Someone also mentioned that interrogating the class hierarchy defeats the OO
principal of encapsulation. I disagree. Encapsulation refers to the
implementation details that are hidden within a class. Classes (and
interfaces) are representative of public interfaces which are considered to
be binding contracts. Interrogating the available public interfaces does not
compromise internal implementation details.

[ray]

Mike Roberts

unread,
Jul 11, 2002, 7:26:03 PM7/11/02
to
"Raybert" <senorv...@yahoo.remove.com> wrote:
> Someone also mentioned that interrogating the class
> hierarchy defeats the OO principal of encapsulation.

That was probably me...

> I disagree. Encapsulation refers to the implementation
> details that are hidden within a class. Classes (and
> interfaces) are representative of public interfaces which
> are considered to be binding contracts. Interrogating the
> available public interfaces does not compromise internal
> implementation details.

I don't entirely disagree; indeed, it's a common (and officialy sanctioned)
idiom in java programming to switch on the presence or absence of an
interface on an instance. I think it's more accurate to say, though, that
switching on class doesn't *necessarily* break encapsulation, because it
really can break encapsulation in a lot of cases. That's why I've called
class switching a red flag: it's not inherently evil, but it's frequently
troublesome.

Let me go back to an example someone proposed earlier in the thread: >CAST
DEATH SPELL, which kills everything living that's within range of the spell.
The original proposal for implementing this was to look for everything of
class Animate, and kill it. Well, what happens if you want to add some
plants, and you want those to wilt in response to the spell? Or, suppose
you have a robot that's Animate but isn't living, so you don't want it
affected. You could add a Plant class, and go back and change the DEATH
SPELL code to look for both Animate and Plant, and exclude the robot. You
could also change the hierarchy so that Plant and Animate have a common
Living base class, and do something with multiple inheritance for the robot,
then change the DEATH SPELL code to look for Living. In any case, you're
going to have to change the DEATH SPELL code, which is way outside the
definition of any of these classes - if you wrote that code some time ago,
you might forget about it until the problem shows up in testing. That's the
practical problem that I'm talking about when I go on about how switching on
class breaks encapsulation.

Now, you could argue that this is a bad example because the class hierarchy
was just designed wrong in the first place. Nonetheless, this is exactly
how development often proceeds - you get to a certain point in the project
and realize you have to go back and change a bunch of stuff you wrote back
at the beginning, either because you made some incorrect assumptions or
because your needs changed mid-stream. Or, in the case of library code,
this can come up when someone takes your code and extends it in ways you
didn't anticipate. In any case, if all the code for Animate were together
in the Animate class, it'd be easier to find all the code affected when you
change the hierarchy.

Another solution is to avoid switching on class, and test a property
instead. You could, for example, use an isAffectedByDeathSpell property to
determine if the spell affects an object; then, if you want to add a whole
new kind of object (say, plants) affected by the death spell, just set the
property on the new objects. Animate would have it set by default; the
robot, which is Animate but not living, could simply override the property
to make itself unaffected. This is what I was getting at in my defense
(earlier in the thread) of testing property flags ("isObstacle") instead of
classes.

I still think the best solution is a polymorphic approach, though. You
could, for example, define a handleSpell(spell) method on your basic Thing
class, and have each object define its own response according to spell; or,
you could have a separate method per spell - handleDeathSpell(), etc.
Either approach would keep the code for a given class's response in that
class, saving you the trouble of hunting down places where you've switched
on the class if you should change the hierarchy.

In any case, all of this is a lot more important for libraries than for
games. Libraries are better when they avoid making assumptions about which
class does what, because those assumptions make it difficult or impossible
for library extensions and games to add their own specialized classes. In a
game, one usually doesn't design with the same expectation of re-use, so the
extra effort of finding a polymorphic solution might not always be
justified.

OKB (not okblacke)

unread,
Jul 11, 2002, 9:51:18 PM7/11/02
to
Raybert wrote:

> Polymorphism is an OO feature whose purpose is to
> generalize specialized capabilities *when it is desirable
> to do so*. It is NOT always desirable to generalize and OO
> does not require that you always do so. Furthermore, not
> all specializations are capable of being generalized (as
> we're seeing in this discussion).
>
> We have a situation in IF game programming where we want to
> generalize some aspects of our game objects some of the
> time, but we cannot do so (reasonably) all of the time.
> This is actually not all that uncommon of a situation and
> it is not necessarily indicative of a bad OO design: it's
> just the natural conclusion to modeling a very complex
> world.

I think IF libraries are a special case of the OO issues you're discussing here, because most IF
libraries DO want to generalize pretty much everything. Game authors expect to be able to combine any
and all classes without having to scratch their heads and reason out all the OO design issues. In other
words, if you're writing some kind of communication protocol library or something to be used by other
programmers, they won't expect to be able to, say, piggyback all 10 supported protocols in a single
transmission. But if you're writing an IF world model, IF authors WILL expect to be able to combine
death spells, doors, human NPCs, hollow glass cubes, and robots in a single room, and will expect
everything to work right.

In a sense, the "combinatorial explosion" that troubles game authors extends to IF library authors
too. If a library includes support for a "kill all" spell, it has to handle how that will interact with all the
existing object classes -- and not only that, but has to make sure it will work for new branches the game
author adds to the class hierarchy. (As Mike Roberts said, the situation for individual games is different
than for libraries.)

--
--OKB (not okblacke)
"Do not follow where the path may lead. Go, instead, where there is
no path, and leave a trail."
--author unknown

Nikos Chantziaras

unread,
Jul 15, 2002, 4:20:39 PM7/15/02
to
"Mike Roberts" <mjr-S...@hotmail.com> wrote in message
news:htoX8.33$tv3...@news.oracle.com...

> Another solution is to avoid switching on class, and test a
> property instead. You could, for example, use an
> isAffectedByDeathSpell property to determine if the spell
> affects an object; then, if you want to add a whole new kind of
> object (say, plants) affected by the death spell, just set the
> property on the new objects. Animate would have it set by
> default; the robot, which is Animate but not living, could
> simply override the property to make itself unaffected. This is
> what I was getting at in my defense (earlier in the thread) of
> testing property flags ("isObstacle") instead of classes.

I may be wrong with this (again? ;-) but I think you don't see the
difference between the "isAffectedByDeathSpell" and "isObstacle"
properties. The former doesn't give us any information about its
type (or class; for me it's the same), while the latter serves
just this purpose. It's perfectly legal (and safe) to change the
"isAffected" property. But we can't do this with "isObstacle",
because we don't know how other code works; does it switch on the
class, or does it evaluate an "isX" property? What happens if an
Actor object has an "isObstacle = true" property but is not of
class Obstacle? "isObstacle" states that the object is an
obstacle, but it doesn't have the interface of an Obstacle. The
"isAffected" property on the other hand, doesn't say anything
about an object's interface. It simply says that the "death"
spell can safely delete the instance (or move it to the nil
location), replace it with another (a corpse maybe) and print an
appropriate message.

Earlier in this thread, you pointed me to the Tads3-way of doing
things. Since I was not very familiar with it, I took the time
and studied the language and the adv3 library a bit. I found out
that you dropped the "isType" approach. The only class that uses
it is Actor (except I missed other ones). But I won't use Tads 3
in the near future. Not because it's still a bit experimental (I
don't mind using unfinished systems if they are far beyond the
"pre-alpha" status; I started working with C++ in 1994 and even
installed pre-1.0 Linux kernels), but because adv3 is too complex.
adv3 (like Alt for Tads 2) is simply too slow (ca. 300 to 400 ms
to get response to an input on my P133 Linux system). I hope the
library will get a bit faster in the future, since the language
itself is the most powerful and elegant of all the existing
IF-languages I know of. I think this is a very serious issue,
since most people *playing* IF will simply ignore Tads 3 games if
their systems can't handle them. We don't want that to happen.

> In any case, all of this is a lot more important for libraries
> than for games. Libraries are better when they avoid making
> assumptions about which class does what, because those
> assumptions make it difficult or impossible for library
> extensions and games to add their own specialized classes.

In this case the library should clearly document what belongs to
the implementation of a class, and what to the interface. Since
Tads doesn't support private/protected members (which means we
can never have true data encapsulation), the user can't know if
the "isType" property is just an implementation detail that should
not concern him, or if it belongs to the public interface of the
class, in which case he has to be paranoid about the usage of this
property ("isType" vs. "isclass()").

-- Niko

Mike Roberts

unread,
Jul 15, 2002, 6:58:49 PM7/15/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote:
> I may be wrong with this (again? ;-) but I think you don't
> see the difference between the "isAffectedByDeathSpell"
> and "isObstacle" properties. The former doesn't give us
> any information about its type (or class; for me it's the same)
> while the latter serves just this purpose.

Actually, my point was that the isObstacle property is *not* information
about the class. isClass(x, obstacle) tests the class; x.isObstacle tests a
property that says that the object wants to be treated as an obstacle. It
happens that objects of class "obstacle" do define isObstacle=true, but
other objects of unrelated classes could just as well define this property.
If they do so, they'll have to otherwise behave with the same interface that
the rest of the code expects from obstacles, but they don't have to be of
class obstacle.

In C++, implementation and interface are combined into class; in TADS, class
is implementation, and interface is something separate. TADS doesn't have
any syntax for describing interfaces, so this becomes a matter for
documentation rather than for program syntax; in the case of obstacle, the
comments in adv.t explain what is expected of an object claiming to be an
obstacle.

> "isObstacle" states that the object is an obstacle, but
> it doesn't have the interface of an Obstacle.

It could very well be taken to mean that the object has the interface of an
obstacle. It doesn't matter whether or not the object is of class
obstacle - it can still provide the same interface as an obstacle. If an
object wants to define isObstacle = true, then it's incumbent on the object
to live up to its claim by actually defining the necessary methods, but it
doesn't have to be based on class obstacle to do this.

--Mike

Raybert

unread,
Jul 17, 2002, 1:10:43 AM7/17/02
to
Mike Roberts wrote:

> "Raybert" <senorv...@yahoo.remove.com> wrote:
>>I disagree. Encapsulation refers to the implementation
>>details that are hidden within a class. Classes (and
>>interfaces) are representative of public interfaces which
>>are considered to be binding contracts. Interrogating the
>>available public interfaces does not compromise internal
>>implementation details.
>
> I don't entirely disagree; indeed, it's a common (and officialy sanctioned)
> idiom in java programming to switch on the presence or absence of an
> interface on an instance. I think it's more accurate to say, though, that
> switching on class doesn't *necessarily* break encapsulation, because it
> really can break encapsulation in a lot of cases. That's why I've called
> class switching a red flag: it's not inherently evil, but it's frequently
> troublesome.

I think I would need to see a good example to be convinced of this. Again,
classes are public interfaces; they don't expose (encapsulated)
implementation details (assuming they're written correctly).

You could argue, I suppose, that having knowledge of the class heirarchy
(i.e. who inherits from who, etc.) breaks encapsulation, but I would disagree
with that also. If you have to interrogate whether a particular class or
interface exists on an object, by definition you don't *know* what the class
heirarchy is. You simply know that an interface exists (somewhere) and you
wish to know if the object under interrogation implements it.

Keep in mind that a class/interface is supposed to be an irrevocable
"contract". So, if you publish an interface and later revoke it, its shame
on you, not on those who chose to use your interface. ;)


> Let me go back to an example someone proposed earlier in the thread: >CAST
> DEATH SPELL, which kills everything living that's within range of the spell.
> The original proposal for implementing this was to look for everything of
> class Animate, and kill it. Well, what happens if you want to add some
> plants, and you want those to wilt in response to the spell? Or, suppose
> you have a robot that's Animate but isn't living, so you don't want it
> affected. You could add a Plant class, and go back and change the DEATH
> SPELL code to look for both Animate and Plant, and exclude the robot. You
> could also change the hierarchy so that Plant and Animate have a common
> Living base class, and do something with multiple inheritance for the robot,
> then change the DEATH SPELL code to look for Living. In any case, you're
> going to have to change the DEATH SPELL code, which is way outside the
> definition of any of these classes - if you wrote that code some time ago,
> you might forget about it until the problem shows up in testing. That's the
> practical problem that I'm talking about when I go on about how switching on
> class breaks encapsulation.
>
> Now, you could argue that this is a bad example because the class hierarchy
> was just designed wrong in the first place.

I do. ;)

A class called something like RespondsToDeathSpell should be created that
extends Animate. Any objects that respond to it should be based on this
class. Existence of the class doesn't imply any particular reaction -- it
only allows access to the reaction. The reaction itself should be
implemented by the object itself. Subjective reactions should be defined
within the subject's domain. A default reaction (the general case) can be
implemented in the base class (RespondsToDeathSpell) and specific objects can
override it as necessary.


> Nonetheless, this is exactly
> how development often proceeds - you get to a certain point in the project
> and realize you have to go back and change a bunch of stuff you wrote back
> at the beginning, either because you made some incorrect assumptions or
> because your needs changed mid-stream.

Okay, but this doesn't say anything about class switching breaking
encapsulation, does it? :) What happens in this case is that your re-design
breaks class switching and many other things as well, probably. And these
things break because you've reneged on your interface "contract".

And you're right, this certainly does happen in the real world, and probably
fairly often. But that's not an argument for not using language features.


> Or, in the case of library code,
> this can come up when someone takes your code and extends it in ways you
> didn't anticipate.

Isn't that the (or a) point of OO programming though: to allow people to
extend your code in ways you didn't anticipate (except maybe in a general sense)?

If someone is trying to extend your code radically and can't make it work,
that implies one of two things: that their approach is wrong or your original
implementation is wrong. If it turns out to be the latter case and you
change your code to accomodate them, that's just a re-design, as in the case
above.


> In any case, if all the code for Animate were together
> in the Animate class, it'd be easier to find all the code affected when you
> change the hierarchy.

Again though, changing the heirarchy is a re-design. When you do that, all
bets are off. And even if you do change the heirarchy, as long as you don't
break your interface "contracts", your class switching doesn't necessarily
break either.


> Another solution is to avoid switching on class, and test a property
> instead. You could, for example, use an isAffectedByDeathSpell property to
> determine if the spell affects an object; then, if you want to add a whole
> new kind of object (say, plants) affected by the death spell, just set the
> property on the new objects. Animate would have it set by default; the
> robot, which is Animate but not living, could simply override the property
> to make itself unaffected. This is what I was getting at in my defense
> (earlier in the thread) of testing property flags ("isObstacle") instead of
> classes.

This is a reasonable alternative. I like this property/attribute model,
partly because it can be generalized. But it's limited: it only avoids class
switching for the general case. It becomes troublesome when a certain object
wants to except itself from the general case. The general case in your DEATH
SPELL example is either "reaction==DIES" or "reaction==LIVES". If some
object, like your robot, wants to do something intermediate (e.g. some of the
robot's capabilities become compromised, but not all) how do you implement
it? You need an interface of some sort on the object class to invoke that
object's subjective reaction. Thus, it may be better to build a solution
that relies solely on that from the start.


> I still think the best solution is a polymorphic approach, though. You
> could, for example, define a handleSpell(spell) method on your basic Thing
> class, and have each object define its own response according to spell;
> or, you could have a separate method per spell - handleDeathSpell(), etc.
> Either approach would keep the code for a given class's response in that
> class, saving you the trouble of hunting down places where you've switched
> on the class if you should change the hierarchy.

Yes, I think that's the correct approach (I would keep the spell a parameter).


> In any case, all of this is a lot more important for libraries than for
> games. Libraries are better when they avoid making assumptions about which
> class does what, because those assumptions make it difficult or impossible
> for library extensions and games to add their own specialized classes.

I don't agree. A library can't make an assumption about an extension that
you wrote because it didn't exist at the time the library was written. It
certainly can to make assumptions about its own components though. A library
should provide a class structure that allows it to do what it needs to do
while at the same time allowing you to extend it. If a library doesn't allow
you to build the extensions that you want, then the library is a failure (or,
it's also possible that your desired extensions are unreasonable, but that
depends).


> In a
> game, one usually doesn't design with the same expectation of re-use, so the
> extra effort of finding a polymorphic solution might not always be
> justified.

A game programmer probably wouldn't go to the same lengths as a library
writer would but he should still use tools like polymorphism because they
exist to make programs easier to write and maintain.

[ray]

Raybert

unread,
Jul 17, 2002, 1:35:43 AM7/17/02
to
Nikos Chantziaras wrote:
> I may be wrong with this (again? ;-) but I think you don't see the
> difference between the "isAffectedByDeathSpell" and "isObstacle"
> properties. The former doesn't give us any information about its
> type (or class; for me it's the same), while the latter serves
> just this purpose. It's perfectly legal (and safe) to change the
> "isAffected" property. But we can't do this with "isObstacle",
> because we don't know how other code works; does it switch on the
> class, or does it evaluate an "isX" property? What happens if an
> Actor object has an "isObstacle = true" property but is not of
> class Obstacle? "isObstacle" states that the object is an
> obstacle, but it doesn't have the interface of an Obstacle.

This is a problem caused by mixing your solutions. The code should choose
one solution or another, not both at once.

[BTW, are you speaking about a specific language or library implementation
(TADS)? I'm speaking about general programming so forgive me if I miss
something,]


> The
> "isAffected" property on the other hand, doesn't say anything
> about an object's interface. It simply says that the "death"
> spell can safely delete the instance (or move it to the nil
> location), replace it with another (a corpse maybe) and print an
> appropriate message.

The property/attribute method only works for the general case. It is not
useful when subjective reactions are required. For example, if we assume
that inventory actions are always generalized (every object can either be
taken or not, and is hidden or not) we can use attributes to determine
behaviour (attribute:isMovable; attribute:isHidden).

However, the reaction to a death spell cannot be generalized (unless you know
that the game design does not call for any subjective reactions to a death
spell). Thus, the correct way to implement this is to provide an interface
on your objects that allows your code to access the subjective reaction. A
base class can provide the general case and specific class(es) can override
it (this is call "specialization"). This is the whole purpose of inheritance
and is at the heart of OO programming.

[ray]

Raybert

unread,
Jul 17, 2002, 1:45:33 AM7/17/02
to
Nikos Chantziaras wrote:
> I may be wrong with this (again? ;-) but I think you don't see the
> difference between the "isAffectedByDeathSpell" and "isObstacle"
> properties. The former doesn't give us any information about its
> type (or class; for me it's the same), while the latter serves
> just this purpose. It's perfectly legal (and safe) to change the
> "isAffected" property. But we can't do this with "isObstacle",
> because we don't know how other code works; does it switch on the
> class, or does it evaluate an "isX" property? What happens if an
> Actor object has an "isObstacle = true" property but is not of
> class Obstacle? "isObstacle" states that the object is an
> obstacle, but it doesn't have the interface of an Obstacle.

This is a problem caused by mixing your solutions. The code should choose

one solution or another, not both at once.

[BTW, are you speaking about a specific language or library implementation
(TADS)? I'm speaking about general programming so forgive me if I miss
something,]

> The
> "isAffected" property on the other hand, doesn't say anything
> about an object's interface. It simply says that the "death"
> spell can safely delete the instance (or move it to the nil
> location), replace it with another (a corpse maybe) and print an
> appropriate message.

The property/attribute method only works for the general case. It is not

Mike Roberts

unread,
Jul 18, 2002, 2:03:49 PM7/18/02
to
"Raybert" <senorv...@yahoo.remove.com> wrote:
> Again, classes are public interfaces

I see I didn't make myself clear; I'll try again. Implementation and
interface are different things. In C++ and Java (and many other OO
languages), the notion of a "class" combines the two. I'm saying this isn't
an essential element of OO programming; it's possible to have
implementation, and implementation inheritance, completely independently of
interface, in which case class is *not* public interface, but is simply
implementation. If you want an example of an object model that formally
separates the two, look at COM; I'm sure there are some languages that make
the separation explicit as well. TADS doesn't make the separation formal,
in that you can write TADS code that follows the same
class=implementation+interface idiom as C++ etc, but in fact what's really
going on is a little different, in that class doesn't truly imply interface
in TADS - or, to look at this another way, the *absence* of a class in an
object's implementation doesn't imply the absence of an interface.

> I don't agree. A library can't make an assumption about an
> extension that you wrote because it didn't exist at the time the
> library was written. It certainly can to make assumptions about
> its own components though.

You're looking at this backwards. Yes, if an object is of class obstacle
then it is safe to assume that it behaves as an obstacle, i.e., that it
implements the public interface of an obstacle; but that's not the point.
The point is that it's *not* safe, in a language like TADS where
implementation and interface are not mixed into a single notion of class, to
assume that *only* an object of class obstacle can implement the obstacle
interface.

To put this in Java terms, suppose you were building a utility class that
does something with an object that supports Collection semantics. One way
you could implement this would be to look through the java standard library
and find all of the classes that provide the Collection interface, and then
build a conditional test on those classes:

void doSomething(Object *obj)
{
if (obj instanceof LinkedList || obj instanceof Vector || // etc for many
more
// ...
}

Clearly a poor design; the condition is unnecessarily complex, but more
importantly, this would preclude using your utility class with any
application-defined classes that implement the Collection interface. This
is just what you're doing if your library switches on class obstacle in
TADS, though. Since TADS doesn't have any native syntax for describing the
interfaces that an object implements, adv.t evolved the isObstacle idiom as
a substitute.

Nikos Chantziaras

unread,
Jul 18, 2002, 4:42:47 PM7/18/02
to
"Raybert" <senorv...@yahoo.remove.com> wrote in message
news:3D34FBD1...@yahoo.remove.com...
> [...] Existence of the class doesn't imply any particular

> reaction -- it only allows access to the reaction. The
> reaction itself should be implemented by the object itself.
> Subjective reactions should be defined within the subject's
> domain. [...]

I agree. But in Tads, I always find it difficult to decentralize the
code. I think the problem is that the IF-languages are simply too high
level. Let me explain (with an example).

We have three objects, "Fred", "Barney", and "Wilma". All of them are
of the same class, and have a spellHandler() method:

spellHandler(Actor, Spell) = {"Nothing happens. ";}

Someone casts the death spell at Barney (Fred, in this case). Barney's
spellHandler() method gets called: (don't try this at home :)

Barney.spellHandler(Fred, deathSpell);

Barney overrides spellHandler() like this:

Barney.spellHandler(Actor, Spell) =
{
if (Actor == Fred) {
switch (Spell) {
case deathSpell:
"Barney screams: <q>Oh, no! Fred, what are you
doing!</q> and dies in a way too messy to describe. ";
/* Insert code that kills Barney and inform other
actors that he died so they can respond if they
can see this etc, etc, etc... */
break;
default:
pass spellHandler;
}
} else if (Actor == Wilma) {
// etc...
}
}

In other words, Barney has to know every actor in the game, so he can
respond to them if they are casting spells at him. Same goes for all
the other actors. At the end, we have a very difficult to handle
web-like structure. Something tells me that this is not the optimal
solution. Since Tads (and the other IF-systems) allow us to write very
high-level code, we end up trying to implement a game by simply
converting a sample transcript in native code. I think this is wrong.
A better solution *might* be making the spell handler a class, and
having each actor communicate with the others through it, so we can
decentralize (no interweaved [spaghetti-like] code). On the other
hand, moving objective responses out of an object doesn't seem very OO
to me. I'm still new to IF. I must admit that I never faced
situations like this before, neither in C++ nor in Object Pascal.

In Tads, every object is actually of its own class. This means that
Fred, Wilma and Barney are not just instances of a same class, but
classes of their own. After all, they override methods from the Actor
class. In C++, the following line is not class switching:

if (FunctionArg == SomeInstance)

But in Tads it *is* class switching, since every instance is of its own
class (and it must be, since defining a new class for a single instance
would be awkward). I'm confused. Tads is not C++, it's different.
Both languages are OO, but each one in its own way. We need to
rethink. Maybe we shouldn't treat Tads (and the others) as an OO
language, but more as something quite similar, but at the same time
totally different, with its own ways of solving problems.

-- Niko

Nikos Chantziaras

unread,
Jul 18, 2002, 4:45:06 PM7/18/02
to
"Raybert" <senorv...@yahoo.remove.com> wrote in message
news:3D3501AA...@yahoo.remove.com...

> This is a problem caused by mixing your solutions. The code
> should choose one solution or another, not both at once.

The library itself (all Tads 2 libraries in this case) uses both! It's just
that my way of coding is too paranoid. You should see the amount of "can't
happen" code in my programs! What should I do:

if (obj.isObstacle)

or:

if (isclass(obj, obstacle))

or:

if (obj.isObstacle and isclass(obj, obstacle))

or:

if (obj.isObstacle or isclass(obj, obstacle))

To make the right decision, I must know how the library handles this (I must
read the library source; not very intuitive, is it?)

Mike said, that having a property like this one, allows us to have unrelated
classes implement a common interface (like the C++ container classes and
their iterator-interface) without deriving them from the same base. A fine
idea, but I don't see a need for this in Tads. Since he dropped this
approach in the Tads 3 library, I think I'm right.

> [BTW, are you speaking about a specific language or library
> implementation (TADS)? I'm speaking about general
> programming so forgive me if I miss something,]

At first, I thought that generalizing is fine. After doing some research
(because of some previous issues in this thread), I think that "speaking
about general programming" is actually a bad idea. What might be right in
one language, doesn't mean that it's a Good Thing in some other one. See my
reply to your other posting.

-- Niko

Mike Roberts

unread,
Jul 18, 2002, 6:44:19 PM7/18/02
to
"Nikos Chantziaras" <rea...@hotmail.com> wrote:
> We have three objects, "Fred", "Barney", and "Wilma".
> All of them are of the same class, and have a spellHandler()
> method:
>
> spellHandler(Actor, Spell) = {"Nothing happens. ";}
>
> Barney overrides spellHandler() like this:
>
> Barney.spellHandler(Actor, Spell) =
> {
> if (Actor == Fred) {
> switch (Spell) {
> case deathSpell:
> "Barney screams: <q>Oh, no! Fred, what are you
> doing!</q> and dies in a way too messy to describe. ";
> // etc

>
> In other words, Barney has to know every actor in the game,
> so he can respond to them if they are casting spells at him.
> Same goes for all the other actors.

Well, that depends. Do you want a custom reaction per actor-actor relation
like this? If so, then I don't think there's any other way, in theory, to
handle it, than to code NxN handlers. You could factor it different ways,
but it's always going to come down to NxN handlers if you want NxN custom
results.

> A better solution *might* be making the spell handler a class,
> and having each actor communicate with the others through it,
> so we can decentralize (no interweaved [spaghetti-like] code).
> On the other hand, moving objective responses out of an object
> doesn't seem very OO to me.

Like I said, you could slice and dice it numerous ways, but being more "OO"
isn't in itself a very valuable objective, in my opinion, if it doesn't
contribute something to readability, maintainability, or some other
practical virtue. The spell handler of your example above is at least
simple and readable; it's weak on maintainability, in that you have to
update it whenever you add a new actor to the game, but it seems to me
there's just no way around that, no matter what your approach, if you really
want custom results per combination.

If you don't really want custom results for every possible actor-spell-actor
combination, then there are things you can do, even within the framework of
your example, that take advantage of OO design to generalize the code. For
example, if what you really want is a special reaction if one actor throws a
death spell at an actor who thought the first actor was a friend, then you
could move the code down into Actor (rather than putting it in each
individual actor), and use a method to determine the is-a-friend status:

Actor.spellHandler(thrower, spell)
{
if (spell == deathSpell && isMyFriend(thrower))
"<<self.sdesc>> screams: ..." // etc
}

> Tads is not C++, it's different. Both languages are OO, but
>each one in its own way. We need to rethink. Maybe we
> shouldn't treat Tads (and the others) as an OO language, but
> more as something quite similar, but at the same time totally
> different, with its own ways of solving problems.

TADS certainly isn't C++, but then C++ is hardly definitive of
object-oriented programming. It's fair to say TADS isn't a *pure* OO
language, but you could say that about most languages that claim to be OO,
including C++.

However, I don't think it's the language that's bothering you, but rather
the problem domain. A long way back in the thread, when you were pointing
out perceived weaknesses in TADS compared with C++, I invited you to think
about how you'd approach the same tasks in C++, because I didn't think the
issues in question were matters of language features, but rather of program
design. I think the same thing applies here: if you feel like TADS is
forcing you take a non-OO approach to solving a problem, and you think there
ought to be a better way, think about how you'd solve the same problem in
your more-OO language of choice; if you can come up with a more OO solution,
chances are you can find a way to adapt it to the TADS way of doing things.

Kevin Forchione

unread,
Jul 19, 2002, 1:42:18 AM7/19/02
to

"Mike Roberts" <mjr-S...@hotmail.com> wrote in message
news:IwHZ8.19$sk3...@news.oracle.com...

> TADS certainly isn't C++, but then C++ is hardly definitive of
> object-oriented programming. It's fair to say TADS isn't a *pure* OO
> language, but you could say that about most languages that claim to be OO,
> including C++.
>
> However, I don't think it's the language that's bothering you, but rather
> the problem domain. A long way back in the thread, when you were pointing
> out perceived weaknesses in TADS compared with C++, I invited you to think
> about how you'd approach the same tasks in C++, because I didn't think the
> issues in question were matters of language features, but rather of
program
> design. I think the same thing applies here: if you feel like TADS is
> forcing you take a non-OO approach to solving a problem, and you think
there
> ought to be a better way, think about how you'd solve the same problem in
> your more-OO language of choice; if you can come up with a more OO
solution,
> chances are you can find a way to adapt it to the TADS way of doing
things.

Absolutely.

In fact, due to the unique nature of the TADS object model, I believe
there's more flexibility in this respect than a class-based object-oriented
language could provide. The prototype-based nature of the TADS object model
(as I see it), means that once *can* code in the C++ / Java style of
class/object, but it also means that one can do things with traits and
objects that aren't, afaik, legal in those approaches.

It would, perhaps be more constructive for the C++ / Java programmer to
think in terms of every class being an object in TADS. As you point out in
the author's manual, a TADS class is a special case of an object - an object
that is treated differently by the parser and by firstObj()/nextObj() loops,
but in other respects is merely another object (In TADS 3, the way one
instantiates a class / object differ, and there are other small
differences).

In this respect TADS is not 'class/object'-oriented, but
'prototype'-oriented, as I like to view it. For instance, a TADS object
inherits from 'traits' (objects - these objects may also be 'classes' as
indicated by being defined using the class keyword, but they inherit from
objects nonetheless.)

Fascinating things are possible here: Changes made to a trait affect objects
that inherit them. This doesn't happen in C++/Java, because the class is a
static definition that is either instantiated, or treated as an abstract
entity. Not so with TADS. For example, if myObject derives from Thing, then
changing the Thing.isSeen attribute will also change the myObject.isSeen
attribute as well. If myObject defines isSeen directly then the change is
hidden by the inheritance hierarchy, but if myObject does not define isSeen
directly then the change is more exposed.

Likewise, adding a new attribute to a trait means that the object derived
from it also now inherits the new attribute. If Thing doesn't define isFoo,
and during program execution we set Thing.isFoo = true, then &isFoo is added
dynamically to Thing class, and myObject now defines this attribute
(inherited from Thing) as well.

And, as an object is derived from other objects, the TADS 3 ofKind() check
is often more useful than the TADS 2, isclass(obj, class) check. For
instance, foo.ofKind(foo) returns true, whereas isclass(foo, foo) returns
nil. This means that a 'switch' based on isclass() won't recognize object-A
of being of class object-A, while the TADS 3 ofKind means that Bob could be
derived from Fred (or that a clone of Fred could be instantiated) and that a
'switch' using ofKind(Fred) would detect both Bob (or the clone) and Fred as
being appropriate for whatever role we wish to perform an action on.

But most importantly, as you point out, the problem domain for interactive
fiction is extremely complex, and a blind adherence to object-oriented
principles may not lead to the most elegant or practical solution.

C++?! Heh! You ain't in Kansas anymore guys...

--Kevin


Stephen L Breslin

unread,
Jul 19, 2002, 1:07:49 PM7/19/02
to
>> [...] Existence of the class doesn't imply any particular
>> reaction -- it only allows access to the reaction. The
>> reaction itself should be implemented by the object itself.
>> Subjective reactions should be defined within the subject's
>> domain. [...]

You appear to be trying to describe a problem, but what you're really
doing is making a rule. Also, this rule sounds too strict a rule to be
of any practical use. In other words, my response is "that depends." I
guess it will be useful to look at examples.

>I agree. But in Tads, I always find it difficult to decentralize the
>code. I think the problem is that the IF-languages are simply too high
>level. Let me explain (with an example).

I don't understand your example, I think because it's inelegant, and
therefore it's not demonstrating what you want it to. I will "correct"
the code, so you can see what I mean.

>We have three objects, "Fred", "Barney", and "Wilma". All of them are
>of the same class, and have a spellHandler() method:
>
> spellHandler(Actor, Spell) = {"Nothing happens. ";}
>
>Someone casts the death spell at Barney (Fred, in this case). Barney's
>spellHandler() method gets called: (don't try this at home :)
>
> Barney.spellHandler(Fred, deathSpell);
>
>Barney overrides spellHandler() like this:
>
> Barney.spellHandler(Actor, Spell) =
> {

// I'm commenting the next line out
> // if (Actor == Fred) {
> switch (Spell) {
> case deathSpell:
// I'm changing the next line from
> // "Barney screams: <q>Oh, no! Fred, what are you
// to
> "Barney screams: <q>Oh, no! <<Actor.name>>, what are you


> doing!</q> and dies in a way too messy to describe. ";
> /* Insert code that kills Barney and inform other
> actors that he died so they can respond if they
> can see this etc, etc, etc... */
> break;
> default:
> pass spellHandler;
> }

// now this next line isn't necessary
>// } else if (Actor == Wilma) {
> // etc...
// and I'm commenting out the last unnecessary close-brace
>// }
> }

With these changes, the main point you're making in the next paragraph
no longer applies.

>In other words, Barney has to know every actor in the game, so he can
>respond to them if they are casting spells at him.

We handled things in the object 'Barney' such that this is not
necessary.

>Same goes for all
>the other actors. At the end, we have a very difficult to handle
>web-like structure. Something tells me that this is not the optimal
>solution.

With your last point I agree. What I think I would do, or what I
normally do, is generalize everything that can be generalized, and
only let the game objects take care of the matters that cannot be
generalized. In the case of your example, you could put the entire
code in the general spell handler; I think you can generalize the
whole deal. I can think of two options you can add to the general
spell handler: an Actor-specific immunity-to-spell boolean, and
Actor-specific death messages. I think Actor-specific death-messages
was what you had in mind when you made your example, so let's analyse
that for a moment.

You won't need to change whether or not the death-spell kills the
actor, and you won't need to change whether or not the actors in the
viscinity are notified of the death. In each case you'll call the
Actor's death() method (which does not come standard in the library,
but which wouldn't be too difficult to figure out). The only thing
you'll want to make specific to the actor is the message that's
printed for the player. So, for example

Fred: Actor

deathSpellMessage(attacker) {
switch(attacker) {
case Barney:
"'Barney, you rubble head! The cereal is mine!'\n";
"'Not any more!' Barney replies greedily.";
break;
case Wilma:
"Wilma, what about the children!";
break;
default:
ActorDefaultDeathSpellMessage();
}
}

>Since Tads (and the other IF-systems) allow us to write very
>high-level code, we end up trying to implement a game by simply
>converting a sample transcript in native code.

I bet you're making an interesting point, but I don't understand it
yet. The effective goal for an IF game is the text printed to the
screen....

>I think this is wrong.
>A better solution *might* be making the spell handler a class,

Good idea. Or, making each spell a class/object, perhaps a dynamically
created object (a la "events" in Tads 3), and a general spell handler
as another class (a la "event handler" in Tads 3).

>and
>having each actor communicate with the others through it, so we can
>decentralize (no interweaved [spaghetti-like] code).

I like spaghetti-like code, but I don't like specific objects all
doing the same thing, when a more general class can do it. Of course,
you shouldn't take this inclination to an extreme, and make the code
harder to read, more complicated, and such.

>On the other
>hand, moving objective responses out of an object doesn't seem very OO
>to me.

You'd want to keep the object-specific stuff in the object, and move
everything general into 'Actor' or into the spell handler.

>I'm still new to IF. I must admit that I never faced
>situations like this before, neither in C++ nor in Object Pascal.
>
>In Tads, every object is actually of its own class.

That's right. In Tads 2, there are also 'functions,' which are not
objects/classes. Tads 3 is more purely oriented towards objects.

>This means that
>Fred, Wilma and Barney are not just instances of a same class, but
>classes of their own. After all, they override methods from the Actor
>class. In C++, the following line is not class switching:
>
> if (FunctionArg == SomeInstance)

(I don't know what 'class switching' is.)

>But in Tads it *is* class switching, since every instance is of its own
>class (and it must be, since defining a new class for a single instance
>would be awkward). I'm confused. Tads is not C++, it's different.
>Both languages are OO, but each one in its own way. We need to
>rethink. Maybe we shouldn't treat Tads (and the others) as an OO
>language, but more as something quite similar, but at the same time
>totally different, with its own ways of solving problems.

I'm not sure what you mean by "similar but totally different." Clearly
Tads 2 is oriented towards objects, or ObjectOriented, even though it
is not object-only. Like any language, it has its own ways of solving
problems. I'm not sure what you're getting at.

Nikos Chantziaras

unread,
Jul 21, 2002, 4:42:08 PM7/21/02
to
"Stephen L Breslin" <bre...@acsu.buffalo.edu> wrote:
> I don't understand your example, I think because it's inelegant, and
> therefore it's not demonstrating what you want it to. I will
> "correct" the code, so you can see what I mean.

It *does* demonstrate what I want it to; that's why it's ugly. I
didn't want the code to print the same response all the time, like
this:

>cast death at barney
Barney screams: "Oh, no! Fred, what are you doing!" and dies in a


way too messy to describe.

>cast death at wilma
Wilma screams: "Oh, no! Fred, what are you doing!" and dies in a way
too messy to describe.

What I want is:

>cast death at barney
Barney screams: (etc, etc)

>cast death at wilma
As the energy of the spell leaves your hands, Wilma tries to scream.
But it's too late; she dies before even a single word can leave her
lips. All you hear is "Grrlp!".

(Please note that my sense of humor could be offensive to Flintstone
fans; I apologize. I'm influenced by Monty Python.)

> >Since Tads (and the other IF-systems) allow us to write very
> >high-level code, we end up trying to implement a game by simply
> >converting a sample transcript in native code.
>
>I bet you're making an interesting point, but I don't understand it
>yet. The effective goal for an IF game is the text printed to the
>screen....

When the design of the game is finished and we go on implementing it,
most people (I think) imagine a sample transcript, like this:

>cast death at [some person]

and then try to "convert" it in code. The above line is "forcing" us
to use a spellHandler(). Anything more abstract isn't obvious by just
looking at this line.

> (I don't know what 'class switching' is.)

"Switching on class" means that you make execution of code depend on
the class of an object. Since the compiler is able to do this
automaticly (polymorphism), there's no need to do it manually. An
example of class switching in Tads 2:

foo: function( obj )
{
if (isclass(obj, lightsource)) {
obj.sdesc; " is a lightsource. ";
} else if (isclass(obj, item)) {
obj.sdesc; " is an item. ";
}
}

The right way to do this would be defining a method in the "thing"
class and let "item" and "lightsource" override it:

modify thing
identify = {self.sdesc; " is a thing. ";}
;
modify item
identify = {self.sdesc; " is an item. ";}
;
modify lightsource
identify = {self.sdesc; " is a lightsource. ";}
;

Now we don't need class switching:

foo: function( obj )
{
obj.identify;
}

> I'm not sure what you mean by "similar but totally different."
> Clearly Tads 2 is oriented towards objects, or ObjectOriented, even
> though it is not object-only. Like any language, it has its own ways
> of solving problems. I'm not sure what you're getting at.

"Similar" because Tads *is* OO, supports polymorphism, etc.
"Different" because the very nature of the language is IF-oriented.
Just because someone is an excellent programmer in a general-purpose OO
language doesn't mean that he is also a master in Tads (or any other OO
language). For example, class switching is a Bad Thing in C++, but
seems to be normal in Tads, even if Tads supports OO programming. But
Tads isn't a general-purpose language. That's why I think it's "similar but
totally different."

-- Niko

Nikos Chantziaras

unread,
Jul 21, 2002, 4:45:36 PM7/21/02
to
"Mike Roberts" wrote:
> However, I don't think it's the language that's bothering you, but
> rather the problem domain.

Definitely. I think the reason for most questions in this thread, is
the lack of a "Designing And Implementing IF in Tads" book (something
like "Object Oriented Programming in C++" by Josuttis). This kind of
documentation is invaluable for an author. It helps us avoiding
reinventing the wheel, and is a source for "Bad Things" and "Good
Things" for a particular language.

> if you feel like TADS is forcing you take a non-OO approach to
> solving a problem, and you think there ought to be a better way,
> think about how you'd solve the same problem in your more-OO language
> of choice;

After each time I try to do this, I feel more confused than before.
Mostly because I discover that what I'm trying to do is not
possible/elegant/OO/whatever in the other language. As you stated
before, it seems to be the problem domain. We talk about what is good,
bad, OO, etc, in the general case of OO programming. As I write in an
other posting: Tads isn't general case. For example, class switching
in C++ is bad. But I think this is because there's no need in C++ (and
other general-purpose languages) to have classes that are intended to
be instantiated just once.

-- Niko

Raybert

unread,
Jul 31, 2002, 12:58:02 AM7/31/02