incubator-lucy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Marvin Humphrey <>
Subject Re: OO Design -- type checking
Date Tue, 31 Oct 2006 04:33:22 GMT

On Oct 23, 2006, at 6:44 PM, David Balmain wrote:

>  Another really nice result of this approach is that
> we can easily apply filters to any method call, simply by swapping out
> the macro with a function. So for example, if we want our animal to
> breath in before it speaks (best example I would think of in this case
> :P):
> #define Animal_Speak(self) Animal_speak((Animal *)self)
> void Animal_speak(Animal *self)
> {
>    self->m->breath_in(self); // call Animal's private breat_in method
>    self->m->speak(self);
> }

There are some limits to our flexibility with these macrofied method  
calls, though.

First, the macros evaluate their arguments twice, which is a problem  
if the arguments happen() to cause any side_effects++.

     Dog_Chase_Cat(dogs[i++]); /* uh-oh. */


For core development, I'm not worried -- committers will be expected  
to catch such problems.  However, if we ever try to design a full-on  
public C interface, as opposed to a semi-public interface for  
bindings authors, I'd consider those unsafe macros a design flaw.

Second, while playing around with this idea I've realized that if all  
the macros follow the pattern I'd originally proposed, we'd be giving  
up a hell of a lot of type-checking.  "self" has to be cast on _both_  
the left and the right hand side, or the compiler will complain at  
some point.

Casting on both sides for all method calls basically means treating  
everything, everywhere as a void*.  Bad.

     #define Animal_Speak(self) ((Animal*)self)->speak((Animal*)self)

     Animal_Speak(dirty_old_shoe);  /* no compile-time warning! */

But then, without the double-cast, this function is problematic  
regardless of whether you use Ferret-style or KinoSearch-style  

     Dog_chase_cat(XXXXX *self)

With Ferret-style inheritance, we cast the left-hand side but not the  

     #define Animal_Run ((Animal*)self)->run(self);

If "self" is a Dog*, the compiler complains, because the "run" method  
expects an Animal* as its first argument and we've given it a Dog*.   
If "self" is an Animal*... well, then Dog_Bark will cause a compile- 
time error because Animal doesn't have a "bark" method.

We're similarly screwed with KS-style inheritance, where we cast the  
right-hand side:

      #define Animal_Run(self) (self)->run((Animal*)self)

Dog actually has a run member, so we don't have to cast the left-hand  
side to avoid the compile-time error.  However, we're going to get  
warnings about the right-hand side no matter what:

   * We can't make "self" a Dog*, because the Animal_run()
     function expects an Animal* as its first argument and
     if we give it a Dog* the compiler will complain.
   * We can't make "self" an Animal*, because the Dog_bark()
     function expects a Dog* as its first argument and
     if we give it an Animal* the compiler will complain.

The only way we can make these macrofied method calls work AND get  
decent type-checking, I've concluded, is...

   1. Use KinoSearch-style inheritance.  (I'd rather use Ferret-style,
      but I don't see a way to make it work with type-checking)
   2. Generate a macro for EVERY method a class has, whether
      it's inherited or not.

This works:

     #define Dog_Run(self) (self)->run((Animal*)self)

     Dog_chase_cat(Dog *self)

... and we get decent type safety!

Only one problem left.  We have to generate a boatload of macros.

The solution is a code generator.  I've got one in the works, but  
that'll wait for another email.

Marvin Humphrey
Rectangular Research

View raw message