Cicada also supports a sort of inheritance. The syntax is different from that in C++: we use a colon ‘:’ to separate the parent object from the code that specializes it. For example, to derive an object from myClassObject we could write:
myDerivedClassObject :: myClassObject : {
newString := "hello, I am a new string"
}
myDerivedClassObject has all of the members of the original myClassObject such as data and otherMethod() (see previous section), as well as the new string member newString.
In this last example, we specialized a predefined parent object with inlined code within braces, using a single inheritance operator. But one doesn’t have to follow this pattern; we can combine class objects and inlined codes in any number and any combination.
A :: { var1 :: int }
B :: { } : { var2 :: string }
C :: A : B
D :: B : A
E :: { var3 :: int, var4 :: string } : A : { var5 :: char }
F :: C : { remove var1 }
So for example the type of C is effectively { var1 :: int } : { } : { var2 :: string }, whereas D is { } : { var2 :: string } : { var1 :: int }. The definition of F shows us that it is possible for a derived object to have fewer members than its parent object.
Importantly, the order of inheritance affects an object’s type. C has a different type than D, so C :: D or C = @D will cause a type-mismatch error.
Cicada allows existing objects to be redefined as a different type only if the new type is derived from its original type. We can always specialize a member’s type by adding more inheritance operators at the end of the type specification. All of the following commands will work in order except the last one.
C :: A : B | we already did this, but fine
C :: C | fine -- C equals A : B
C :: C : B | OK -- now C will be A : B : B
C :: A : B : B : {} | OK
C :: A : B : B : {} | error!
The last line failed only because any two inlined types are presumed different -- Cicada is not in the business of comparing what’s inside those braces to see if they match up.
Although types can be specialized, they can never generalized. So typing F :: C will cause a type-mismatch error.
The same type-matching rules apply to aliases.
D :: B : A | was already defined this way
G :: B : A : { }
D = @G | legal
(D =@ *) :: B : A | legal
G = @D | NOT legal!
Aliasing doesn’t change a member’s type, which explains why we could reassign D to a new variable of its original type.
It turns out that the inheritance operator can derive new types for any composite Cicada object: variables, sets, even functions. Inheritance of sets is best thought of as a concatenation of these sets.
a :: { Alice, Bob }
b :: { Charlie, David }
c :: a : b
So c contains Alice, Bob, Charlie, David, in that order.
Inheritance of functions basically tacks new code at the end of the old (parent) function. Each sub-code keeps its own original search path (the example of Figure 3 shows a situation where these may be different). Function-inheritance makes the most sense when the function does not return a value (i.e. it’s a subroutine), because any return statement will prevent the new code from running. The resulting function contains members from both parent functions.
absval :: {
sign :: int
code
sign = 1
if args[1] < 0 then sign = -1
args[1] = that*sign
}
sqrt :: {
code
return args^0.5
}
modulus_sqrt :: absval : sqrt
Here is a better version of this example, where we use the inheritance operator to specialize a function using a composite object that has no coding section (no code marker).
sqrt :: {
f :: { ; return args[1] }
code
return f(args[1])^0.5
}
modulus_sqrt :: sqrt : {
remove f
f :: { ; return abs(args[1]) }
}
So the inheritance operator can conjoin different types of objects: functions, sets, classes, basically any composite object. To see how this works, think of any non-function as the constructor part of a function (for example, imagine putting a code marker at the very end). Inheritance in Cicada is really a concatenation of code, whether that be constructor code (the commands before the first code marker), the first coding block, or any subsequent code blocks. In Cicada type is code. Incidentally, this explains why objects can be specialized but not generalized: we can always do new things to an existing object, but there’s no general way to reverse what’s already been done to it.
Last update: May 8, 2024