Yazoo ---> Online Help Docs ---> Yazoo scripting ---> Classes and Inheritance

Inheritance

The one operator that we have entirely ignored so far is the inheritance operator, which has the symbol `:'. In Yazoo, inheritance can be applied to most objects, including functions, but its most familiar use is probably in the context of deriving classes. The syntax is different from that in C++. The phrase `a : b' creates a type, derived from a, and refined by the additional code `b', as in:


    parent_class :: {
       data_1 :: ulong
       func_1 :: { ... }
    }
   
    child :: parent_class : {
       func_2 :: { ... }
    }
   

We defined child in the normal way, using the familiar define operator, but its type is a conjunction of the parent's type and some new component unique to the child. The members data_1 and func_1 are common to both parent_class and child, but func_2 is specific to child.

Before going any further, we should make clear that inheritance applies only to composite objects. Well, `composites' is a broad class; maybe a better statement is that the only entities that do not participate at all in any sort of inheritance are the primitive variables and data types (double, string, etc.). So both of the following are illegal.


    x :: { ... } : ulong    | both completely illegal
    y :: ubyte : string
   

In our earlier example, the left-hand argument of the inheritance operator was a predefined class, while the right-hand argument was inlined code within braces; this is the familiar case in which a pre-defined generic type parent_class is deepened by a type specific to the new object child. But one doesn't have to follow this pattern; in Yazoo, each type may be a predefined class or a new type in braces, independently of the other. Think of the `:' as a type-concatenation operator, conjoining the members from both left- and right-hand arguments, irrespective of whether they are inlined or not. We can, for example, `derive' a different child class:


    cousin : {
       func_2 :: { ... }
    } : parent_class
   

Although child and cousin have been endowed with the same set of members, there are two important differences between them. 1) Types are concatenated left-to-right, so their members are in a different order. child[1] is data_1, while cousin[1] is func_2. 2) The left-to-right ordering of types creates an arrow of inheritance, so by Yazoo's reckoning cousin is not really a child of parent_class. Who is descended from whose tribe determines the ways that types may be specialized, as we explain below.

Suppose that we define some instance of a class


    Bob :: parent_class
   

If we were sloppy we may have put this after the code marker of a function, or inside of a loop, in which case Bob might be redefined many times. This is fine -- you can redefine anything as often as you like provided that you don't change its type.


    Bob :: parent_class     | pointless, but legal
    Bob :: parent_class
    Bob :: parent_class
   

You can also specialize the type, by concatenating types after, but not before, the original type.


    Bob :: parent_class
    Bob :: parent_class : { ... }   | legal
   

Line 2 adds whatever additional members are contained in its braces to class instance Bob. But there is no turning back -- you can never generalize a type. So the following would be illegal.


    Bob :: parent_class
    Bob :: parent_class : { ... }
    Bob :: parent_class     | illegal! type mismatch
   

The rule is that, when redefining an object that already exists, each component of the existing type must be present, in the same order, in the new type. Only after all existing types have been recapitulated may we add (any number of) additional type specifications. The additions will be permanently added to the object's and/or member's type specifications (different define operators update different combinations of these).


    a :: { ... }, b :: { ... }, c :: { ... }
    d1 :: a, d2 :: b:c
    x :: d1 : d2
    x :: a : b : c     | this is all perfectly legal
   

One potential point of confusion is that two separately-written inlined types are always considered different even if they are letter-for-letter completely identical. So, the following code will cause a type-mismatch error.


    Joe :: { a :: ulong }
    Joe :: { a :: ulong }   | t-m error
   

However, we have seen that the following is legal:


    for counter in [1, 2]
       Joe :: { a :: ulong }
    end for
   

Only separately-written (hence independently-compiled) pairs of braces are considered different codes. The compiler assigns a unique ID to each pair of braces it sees, and unfortunately it is not in the business of comparing what's in these braces to see whether they're compatible, so it assumes any two separate brace-pairs are totally unrelated. (For this reason earlier versions of the program would throw type-mismatch errors when the user executed a script file twice, unless each class or function definition object :: ... at root level was preceded by an explicit trap(remove object) in the script. The modern version of run() is intelligent enough to delete object before the script is rerun.)

Thus far we've been speaking in the language of classes to describe the effect of the inheritance operator. But, as we said at the beginning, inheritance works with most objects in Yazoo. It works with ordinary composite types and variables, obviously, since those are just a special case of classes (classes without functions). Inheritance also works with sets. In the case of sets the operator represents a true concatenation.


    a :: { Alice, Bob }
    b :: { Charlie, David }
    c :: a : b
   

So c contains Alice, Bob, Charlie, David, in that order.

In all of our examples so far, each piece of code in the type specification has simply added members to the new variable or class. But these `constructors' can just as easily destroy members as create them, so it is possible for the child to have fewer elements than the parent.


    chocolate :: {
       cacao_solids :: protein
       cacao_butter :: fat   }
   
    white_chocolate :: chocolate : {
       remove cacao_solids   }
   

We can perform equates, run loops, or do any other valid Yazoo operation when specializing a type.


    tel_number :: {
       prefix :: 3_dig :: 4_dig :: ulong   }
   
    DC_number :: tel_number : {
       prefix = 202   }
   

Since constructors are evaluated left-to-right, in neither of the last two examples could their order be reversed (the remove would fail in the former; prefix would not yet exist in the latter). These examples, by the way, demonstrate why Yazoo cannot un-specialize a variable, as many of these specializing operations are irreversible.

Finally, we can even apply the inheritance operator to functions. In practice this is often awkward and not terribly useful; inheritance is most useful for subroutines that do not return a value. The constructors (the part before the code markers) of two functions are concatenated as usual, and the codes (following the code markers) are run one after the other when the function is called. An example is given below.


    absval :: {
       sign :: sbyte
       code
       
       sign = 1
       if args[1] < 0, sign = -1, endif
       args[1] = that*sign
    }
   
    sqrt :: {
       code
       args[1] = that^0.5
    }
   
    modulus_sqrt :: absval : sqrt
   

This would not work if the original function had a return statement, since Yazoo would never reach the second code. What we just did is cumbersome because we can't return the final value. A more promising approach might be to sequester any potentially heritable pieces of code into functions which can later be replaced.


    sqrt :: {
       f :: { ; return args[1] }
       code
   
       return f(args[1])^0.5
    }
   
    modulus_sqrt :: sqrt : {
       remove f
       f :: { ; return abs(args[1]) }
    }
   

One particular Yazoo stunt is to specialize an object that has no code marker, like a composite variable, with a type definition that does contain a code marker (i.e. a function). After this remarkable operation, the thing whose mother was a variable has acquired characteristics of a function, and you can run it and see for yourself that it will indeed run the newly-inherited code. Clearly, Yazoo blurs the distinctions between functions and variables. Clarifying this situation will be one of the first orders of business of the next chapter.


Prev: Classes   Next: Yazoo bytecode


Last update: July 28, 2013

Get Yazoo scripting language at SourceForge.net. Fast, secure and Free Open Source software downloads