Inheritance and interfaces
Contents
Inheritance and interfaces#
Inheritance#
A fundamental idea in OOP is that you can make a more sophisticated object based on a simpler or more general object. For this we have derived or “child” classes. Notice the difference between an instance and a derived class. The instance also inherits from class, but it is fundamentally of the same nature as its sibling instances. A derived class is a new class that you can make instances of. They inherit the members of the base class (although the derived class’s code may overwrite base members), but cal also have additional features. Instances of a derived class are also instances of the base class.
Here’s an example:
]dinput
:Class Person ⍝ Person base class
:field heightVal
:field weightVal
:field ageVal←0
∇ Birth(h w)
:Access public
:Implements constructor
(heightVal weightVal)←h w
∇
:Property height,weight,age
:Access public
∇ r←Get x
r←⌊⍎x.Name,'Val'
∇
:endproperty
∇ Grow cm
:Access public
heightVal+←cm
∇
∇ Gain kg
:Access public
weightVal+←kg
∇
∇ Lose kg
:Access public
weightVal-←kg
∇
∇ Age y
:Access public
ageVal+←y
∇
:property BMI
:access public
∇ bmi←Get
bmi←⌊0.5+weightVal÷×⍨heightVal÷100
∇
:endproperty
:EndClass
]dinput
:Class American: Person
:field public ssn
∇ Birth(w h)
:Access public
:Implements constructor :base w h
ssn←1↓∊('-'@1∘⍕¨⊢+¯1+?)1000 100 10000
∇
:EndClass
So in the :Class
header line, we have an additional colon (:
) and the name of the base class. An American
is really just another Person
, but with a social security number. The social security number is given at birth, so we have a constructor that sets ssn
. But we can’t just replace the constructor of the Person
class, because it performs some important stuff too, namely initialising the weight and height.
Notice the :base
in the constructor declaration. It tells APL to call the constructor of the base class. w h
is used to propagate the constructor arguments to the base constructor. In this case, we wrote w h
out for clarity, but it could also just have said Birth args … :base args
. APL would have made sure to find the right base constructor (for 2 arguments), and would have thrown an error if the user didn’t supply exactly two arguments.
Of course, you can also have a base class that doesn’t need any arguments to construct, but a derived class that does need arguments. In such a case, you’d have a monadic derived class constructor, with the line :Implements constructor :base
. And, of course, you can have the opposite too, and differing number of args, etc. Mix and match as you see fit.
We can extend our classes further:
]dinput
:Class NorthAmerican : Person
:field public language←'English'
∇ Birth args
:Access public
:Implements constructor :base args
∇
:EndClass
]dinput
:Class American : NorthAmerican
:field public ssn
∇ Birth(w h)
:Access public
:Implements constructor :base w h
ssn←1↓∊('-'@1∘⍕¨⊢+¯1+?)1000 100 10000
∇
:EndClass
]dinput
:Class Canadian : NorthAmerican
:field public sin
∇ Birth(w h)
:Access public
:Implements constructor :base w h
sin←1↓∊('-'@1∘⍕¨⊢+¯1+?)3⍴1000
∇
:EndClass
]dinput
:Class Swede : Person
:field public pin
:field public language←'Swedish'
∇ Birth(w h)
:Access public
:Implements constructor :base w h
pin←(2↓⍕100⊥3↑⎕TS),'-'@1⍕10000+¯1+?10000
∇
:EndClass
So here we have Americans
and Canadians
being derived from NorthAmerican
which is a type of Person
(yes, really). Each “level” adds its features to the final class’s instances.
If you deal with a lot of such derivations, you may want to know the hierarchy of a certain class or instance.
Monadic ⎕CLASS
gives you a vector of refs beginning with the class and ending with the most basic class. You may also want to know the opposite: which instances does this class have? Monadic ⎕INSTANCES
gives you a vector of refs to all the instances of the given class.
c1 c2 c3←{⎕NEW Canadian ⍵}¨(3 50)(4 55)(6 60)
⎕CLASS c1
(c1 c2 c3).⎕DF 'Albert' 'Bert' 'Charlie'
a1←⎕NEW American (7.5 47)
a1.⎕DF 'Dave'
s1←⎕NEW Swede (5 70)
s1.⎕DF 'Erik'
⎕CLASS s1
⎕INSTANCES Person
⎕INSTANCES NorthAmerican
┌────────────┬─────────────────┬──────────┐ │ #.Canadian │ #.NorthAmerican │ #.Person │ └────────────┴─────────────────┴──────────┘
┌─────────┬──────────┐ │ #.Swede │ #.Person │ └─────────┴──────────┘
Albert Bert Charlie Dave Erik
Albert Bert Charlie Dave
There’s another nice system function when dealing with classes (and other scripted objects); ⎕SRC
(SouRCe):
↑⎕SRC cl⊣(⎕FIX':class cl' '∇r←SetDF x' ':access public shared' '⎕DF x' 'r←1' '∇' ':endclass').SetDF'yo!'
:class cl ∇r←SetDF x :access public shared ⎕DF x r←1 ∇ :endclass
Interfaces#
A Dyalog interface is a script (unsurprisingly :Interface…:EndInterface
) which defines some properties and/or methods. Then multiple classes can use a common skeleton framework. This can help ensure a harmonised API.
Consider, for example, the following:
]dinput
:Interface FishBehaviour
∇ R←Swim ⍝ Returns description of swimming capability
∇
:EndInterface ⍝ FishBehaviour
Note that there isn’t any code in Swim
. It is just a stub for the actual class to fill in. Interfaces can also have multiple such stubs:
]dinput
:Interface BirdBehaviour
∇ R←Fly ⍝ Returns description of flying capability
∇
∇ R←Lay ⍝ Returns description of egg-laying behaviour
∇
∇ R←Sing ⍝ Returns description of bird-song
∇
:EndInterface ⍝ BirdBehaviour
Now we can define a class with a base class, which implements these methods:
]dinput
:Class Penguin: Animal,BirdBehaviour,FishBehaviour
∇ R←NoCanFly
:Implements Method BirdBehaviour.Fly
R←'Although I am a bird, I cannot fly'
∇
∇ R←LayOneEgg
:Implements Method BirdBehaviour.Lay
R←'I lay one egg every year'
∇
∇ R←Croak
:Implements Method BirdBehaviour.Sing
R←'Croak, Croak!'
∇
∇ R←Dive
:Implements Method FishBehaviour.Swim
R←'I can dive and swim like a fish'
∇
:EndClass ⍝ Penguin
A derived class can only have a single base class, but you can use these interfaces to have something resembling multiple inheritance. Notice the :Class
line. Animal
is the base class, whereas methods and properties from BirdBehaviour
and FishBehaviour
are included in the Penguin
class.