Inheritance is such a wonderful principle.

For once we are not trying to reinvent everything.. *g*

We use Inheritance in several ways on different levels of abstraction:

  1. psyced uses inheritance in the classic object-oriented meaning to provide for abstractions of psyc semantics from the syntactical details of each access protocol, so that, no matter if you use IRC or telnet or whatever to access PSYC, you are still using fundamentally the same code. Fippo has written a paper on the subject, which is available on the site.
  2. The PSYC protocol itself supports semantic inheritance of message types (or methods) and even variables, which means you can extend the protocol syntax in all points whenever you need it, and the rest of the network will at least know what to do with the new message in the most suitable default way. This is further described in the protocol specs.
  3. The textdb in psyced supports the same keyword inheritance as the protocol also for any entry in the database, so you can make use of it for any template that is output by psyced at any time.

Here's an example of the latter type of inheritance. When the jabber textdb is selected, a _notice_invitation will find the following template:

<message to='[_INTERNAL_target_jabber]' from='[_INTERNAL_source_jabber]'>
 <x jid='[_place]' xmlns='jabber:x:conference'/>

Notice the curly brackets? They indicate an include from a lower level textdb, which may be default/en/plain

[_nick] invites you to [_nick_place].

but it may as well be default/de/plain saying

[_nick] l├Ądt Dich nach [_nick_place] ein.

Also, whenever you send _notice_action_smile_st00pid the textdb will figure out, that it doesn't know anything about st00pid smiles, and will use _notice_action_smile instead, unless you have provided a template in your message yourself - then your template has priority over any almost matching translations.

Interesting, this example shows a wrong inheritance really.. the source's _nick shouldn't be placed in _nick as in theory _nick_place could be a replacement for it. Yes, it's a protocol bug we are discussing in syntax and will be addressed later using entity-oriented state.


How to implement inheritance

The way this is done in practice keeps repeating itself, of course. Since we have thought about this many times, here's a little list of strategies.

The forget-inheritance-and-implement-every-little-method approach

VERY BAD: Fresh client programmers quite often try to handle each and every funny method the server throws at them. This is very bad as it completely kills the usefulness of PSYC inheritance and misses out on the huge advantages (like having just one _failure implementation for every kind of _failure, because you probably won't ever need more than one - the one that outputs the message to the screen using psyctext). In the end you will find yourself throwing away your huge table of methods which is always missing some, simply because some other PSYC developer will invent them while you deploy your program, and rewriting your client to use a smarter method. So, if you are reading this early enough, you are lucky. You can skip the next two wrong approaches too, and go straight for the good ones.

The abbrev() string match

BAD: Doing simple abbrev() operations is in most cases quite inefficient as it forces you to go through all possible matches by a long if-elseif-elseif... chain. Very inefficient. abbrev may however be the tool to use for variable inheritance. See also: API.

The switch hierarchy

SO-SO: You can split the keyword by its _ delimiter into an array, then make hierarchical switch() chains or hash lookups, having one switch for requests, one for notices etc. Usually enough for clients, not so good for servers. Still, this method is unnecessarily complicated. The ones described below are faster and simpler.

The try and slice approach

GOOD: Slightly more work, as you have to do this in every entity, but maybe also more flexible: You still get to keep all method names in a big hash or switch, only in this case you do a loop around it and remove the last piece of the keyword whenever the match fails - so in most cases you will get a direct hit of the keyword at the first lookup attempt and only find yourself retrying for unknown or unusual candidates.

Here's an example implementation of this approach taken from psyced's root entity (now sharing macros from psyc.h with other parts of psyced):

int msg(mixed source, string mc, mixed data, mapping vars) {
	string family = mc;

	while (family) {
	    switch(family) {
	    case "_notice_update_news":
		... do whatever specific...
		return 1;
	    case "_notice":
		... do whatever generic...
		return 1;
	    case "_failure":
		... do whatever ...
		return 1;
	    case ... various supported methods and generic families ...
	    int glyph = rmember(family, '_');
	    if (glyph > 1) family = family[.. glyph-1];
	    else break;
	... encountered completely unexpected method ...
	return 0;

As you can see it only reaches the inheritance processing if the direct attempt to find the method failed. As long as there are families left in the method it will run the switch again. In this example the matching methods are supposed to bail out with a return 1 while the default code for unexpected method appears after the while statement. Yes, the while could be a while(1) - I just thought it looks more readable like this.

Here's a variant in javascript, which does not require to break out using "return" and therefore uses glyph with a magic value of -4 to figure out what happened:

       var family = mc;
       while (family) {
               var glyph = -4;
               switch (family) {
               case "_message":
                       if (data) Say("("+nick+") "+data);
               case "_request":
               case "_notice":
               case "_error":
               case "_failure":
               case "_info":
               case "_warning":
               case "_status":
                       if (data) Warn("["+nick+"] "+data);
                       glyph = family.lastIndexOf("_");
                       if (glyph > 1) family = family.slice(0, glyph);
                       else family = 0;
               if (glyph == -4) family = 0;    // handled successfully
       if (glyph != -4) { // something arrived that didn't fit *any* family
               Warn("Received unknown method family "+ mc +" from "+ source);
       // do more things in life

The know-my-methods-in-advance approach

GOOD: This solution is planned for psyced as soon as the big rename is through and we have compact keywords. Since the parser needs to expand all compact keywords into its long forms for internal operations, it should know all methods and variables that will be used within the system. Thus, whenever it encounters something unexpected, it can reduce it to the next known keyword at parsing time, and keep the original name somewhere in an _INTERNAL_method variable. After that, handling methods anywhere in the system can be done by a simple hash lookup.

It may also prove good to combine the above two strategies where suitable. In fact they are the same strategy applied in two different places.

In most programming languages it shouldn't make much difference if you use a hash data structure (a mapping), an object's method table or a switch() statement, but you may want to check for safety. For instance in LPC I was surprised to find that the object table was the only true hash, any other method is actually implemented as a binary search to save memory. Probably a reason why Pike is typically faster.

Match freely

Sometimes inheritance isn't semantically flexible enough, then you need a psycmatch function.