2011-02-15

Dynamic Features in EnScript

EnScript is first and foremost a static language. It's statically typed and statically compiled, which means that EnScript figures out everything in your script before it starts to execute it, and if it cannot figure out which code should be called or finds any other syntactic problems, it generates a compilation error. It also means that EnScript executes reasonably efficiently, since the compiler has figured out which functions to call in advance, and this is done in a relatively lightweight manner.

(It should be noted that EnScript does not do much in the way of code-optimization, however, so dynamic languages that make use of just-in-time compilation and other aggressive techniques may yet execute faster than EnScript.)

Despite being squarely in the static camp of languages, EnScript does have some features which can be considered "dynamic." By dynamic, I mean that you can write an EnScript that is able to interact with the EnScript engine and script code, at least in some ways.

The dynamic features in EnScript are:
  • Typecasting
  • Class reflection
  • Property accessors
  • Subordinate execution through ProgramClass
These dynamic features make EnScript far more powerful than it initially seems. I'll be writing about these features over the next few weeks. Since typecasting is short and sweet, I'll cover that now.

Upcasting

Let's say you're working with a NameListClass object. Since NameListClass inherits from NodeClass, you can always treat a NameListClass object as a NodeClass object without doing anything special:

NameListClass list();
list.Parse("one two three", " ");

NodeClass nodeRef = list; // up-cast
Console.WriteLine(nodeRef.Count());

This is an example of upcasting, where we treat a derived object as its parent type by manipulating it through a parent-type reference. EnScript knows that NameListClass inherits from NodeClass, so there's no need for you to do anything special, and this cannot possibly fail. There is no ambiguity.

Downcasting

What if we want to do the reverse? Say we have a function that takes a NodeClass object as a parameter, and we'd like to treat it specially if it's a NameListClass object? Here's the answer:

void foo(NodeClass node) {
  NameListClass listRef = NameListClass::TypeCast(node);
  if (listRef) {
    // ...
  }
}

As it turns out, every class in EnScript has a static function named TypeCast(). TypeCast() takes an ObjectClass reference and returns a reference to an object for its particular type, e.g., NameListClass::TypeCast() returns a NameListClass object reference. Because every object inherits from ObjectClass implicitly (even NodeClass), you can pass just about anything into TypeCast(), and because NameListClass::TypeCast() returns a reference to a NameListClass object, EnScript's compiler doesn't complain about assigning the result to the listRef reference here. TypeCast() lets us bridge the gap, allowing us to treat our NodeClass as a NameListClass.

Note here that we then check listRef to see whether its null. Why? Well, what if someone called foo() and passed in an EntryClass object? An EntryClass object is not a NameListClass object, so NameListClass::TypeCast() will return null. By doing so, you can safely test your downcast. Of course, if you didn't do the check, the code would compile but you'd end up with a null reference error when someone called foo() with anything other than a NameListClass object.

One more thing about TypeCast(): Don't pass it a null reference. TypeCast() itself will generate a null reference error if you pass it null. Therefore, to be truly safe, foo() should look like this:

void foo(NodeClass node) {
  if (node) {
    NameListClass listRef = NameListClass::TypeCast(node);
    if (listRef) {
      // ...
    }
  }
}

Single Object Inheritance is the Root of All Evil

If you make your own class hierarchies, you will find yourself having to make use of TypeCast() quite a bit. Let's say you have an AnimalClass as a base class and two derived classes, DogClass and CatClass. Further, we want to have a method to check whether two AnimalClass objects are equal (...uh, whatever that means...). So, we would have to create a virtual function in AnimalClass, isEqual(), that looks like this:

class AnimalClass {
  pure bool isEqual(AnimalClass other);
}

and then override the function in DogClass and CatClass. Clearly, a dog is not ever going to be equal to a cat. But not all cats are equal to each other, either. We will need to check their favorite brand of kitty food, kitty litter, markings, meowing habits, and whatnot to decide two cats are equal. So, even though isEqual() receives an AnimalClass object, we'll need to treat it as a CatClass to inspect all the member variables that are specific to cats and make this determination.

class CatClass {
  String FaveWetFood, FaveDryFood, FaveLitter;

  virtual bool isEqual(AnimalClass other) {
    CatClass cat = CatClass::TypeCast(other);
    if (cat) {
      return FaveWetFood == cat.FaveWetFood
        && FaveDryFood == cat.FaveDryFood
        && FaveLitter == cat.FaveLitter;
    }
    return false; // must be a dog or a ferret
  }
}

The TypeCast() ends up being necessary because there's something common we want to do for all animals (e.g., make equality comparisons), so the functionality must go in the base class and the function signature must involve an AnimalClass object. In languages like Java, SmallTalk, and EnScript, programmers end up having to do this a lot.

In C++, which has templates, you can often make use of the Curiously Recurring Template Pattern.

No comments:

Post a Comment