Properties
Contents
Properties#
So far, classes have acted pretty much like restricted namespaces. Properties act much like fields/variables, but allow us to take special action when they are set or used.
Get and Set#
Have a look at this code:
]dinput
:Class Person
:Field public name←'-'
Upper←1∘⎕C
Lower←⎕C
:Property Name
:Access Public
∇ text←Get
:If '-'≡name
text←'I don''t have a name!'
:Else
text←'Hi, my name is ',name,'!'
:EndIf
∇
∇ Set text
name←(Upper 1↑text.NewValue),(Lower 1↓text.NewValue)
∇
:EndProperty
:EndClass
Upper
and Lower
are two functions (methods) which just uppercase and lowercase. Then we have a block which defines the property Name
. It doesn’t matter that it only has casing difference from the name field, but it is convenient to remember their connection. The way properties work is that they have 1–3 specially named functions. Here, Name
has Set
and Get
. The Get
and Set
functions have to be named thus, but you may case them as you want, to fit with whatever coding style you choose. The third one is called Shape
, but it only applies to a special kind of properties which we won’t cover.
Name
will be treated as a public (due to the :Access
declaration) field, but instead of directly setting a variable, the Set
function will be called whenever one uses assignment syntax for Name
. However, Set
doesn’t just get the new value as argument. Rather, it gets a namespace with some members (you’ll see later why). The important member here is NewValue
, as you can see.
Get
is called when one attempts to use the value of Name
.
In the code abobe, :Field
initialises name
to be a dash. Get
will check whether name
is a dash or not, and respond accordingly. Set
will accept a character vector and make sure the casing is right (upper initial, rest lower) before assigning to name
. Let’s see if it works:
p←⎕NEW Person
p.name
p.Name
p.Name←'anTON'
p.Name
p.name
Multiple properties and Default#
A class can have more than one property. Let’s have a look at a fancier version:
]dinput
:Class Person
:Field age←0
:Field name←'-'
:Property Age
:Access Public
∇ num←get
num←⌊age
∇
:EndProperty
∇ Grow amount
:Access Public
age+←amount
∇
Upper←1∘⎕C
Lower←⎕C
:Property Default Name
:Access Public
∇ text←Get
:If '-'≡name
text←'I don''t have a name!'
:Else
text←'Hi, my name is ',name,'!'
:EndIf
∇
∇ Set text
name←(Upper⊃text.NewValue),Lower 1↓text.NewValue
∇
:EndProperty
:EndClass
There are three changes here. The most obvious one is the Age
property and the complementary method Grow
. The third change is the Default
declaration for the Name
property. Normally, objects are passed by reference while arrays are passed by value. But the monadic ⌷
called Materialise has the ability to transform references into values. So if a method has a Default
property, then monadic ⌷
will yield this property.
Let’s look at those changes in action:
p←⎕NEW Person
p.Name←'BRUNO'
p.Age
p.Grow 3.6
p.Age
p.Grow 0.6
p.Age
⌷p
On the topic of monadic ⌷
, if you apply it on .NET collections, it materialises the collection’s items, returning an array of the .NET items that the collection consisted of. You can of course make your class have that same behaviour by setting the default property appropriately.
Generic properties#
Sometimes a class needs a few properties that have the same or similar getter and setter. Instead of repeating yourself, Dyalog APL lets you collapse the code into a single :Property
block:
]dinput
:Class Person
:field heightVal
:field weightVal
:field ageVal←0
:Property height,weight,age
:Access public
∇ r←Get x
r←⌊⍎x.Name,'Val'
∇
:endproperty
:EndClass
Notice the comma-separated “name list”. You can also see why the argument to Get
needs to be a namespace: so that we can determine which property was requested. Here’s a complete listing of the Person
class:
]dinput
:Class Person
: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
p←⎕NEW Person (50 3)
p.Gain 0.7
p.weight
p.Grow 2.5
p.Gain 0.4
p.weight
p.BMI
Display form#
The normal display of an object is with a namespace path and object name or class name/”namespace” in brackets. Not very useful:
⎕NS ⍬
However, the system function ⎕DF
(Display Form) allows you to change this to any character array:
ns←⎕NS ⍬
ns.⎕DF 2 2⍴'yo'
ns
This is similar in spirit to Python’s “dunder” method __repr__(). Of course, having a static display form like that isn’t much fun. Here is a better usage:
]dinput
:Class Person
∇ Birth
:Implements constructor
:Access public
⎕DF 'baby'
∇
Upper←1∘⎕C
Lower←⎕C
:Property Name
:Access Public
∇ text←Get
:If 0=⎕NC'name'
text←'I don''t have a name!'
:Else
text←'Hi, my name is ',name,'!'
:EndIf
∇
∇ Set text
name←(Upper⊃text.NewValue),Lower 1↓text.NewValue
⎕DF name
∇
:EndProperty
:EndClass
Now we have a constructor which sets up the initial display form. And every time the Name
property is Set
, the display form is updated.
p←⎕NEW Person
p
p.Name
p.Name←'anTON'
p.Name
p
As we now know, objects are passed by reference. This means that if we just try to grab the object value, we get a ref rather than the display form, even if the display form is what shows in the session. How do we get the actual display form? In C# it would be ToString, of course. Think about it: if you have a numeric array, how would you get the character array display form? Well, Format (⍕
) is APL’s “ToString”. So ⍕object
will give you whatever argument has been fed to ⎕DF
:
p ⍝ Still a reference, even if it displays as a character array
⎕NC 'p' ⍝ Name class 9: object
1⎕C⍕p ⍝ ⍕p is the actual ⎕DF: we can for example upcase it
Overtaking objects#
Another cool thing you can do is overtaking. Remember how APL pads with the a fill element if there are not enough elements to go?
10↑3 1 4 ⍝ Overtake a list of ints pads with 0
If a class has a niladic constructor, then overtaking an instance will create siblings (i.e. new instances of the same class) using the niladic constructor:
]dinput
:Class Person
∇ Birth
:Implements constructor
:Access public
'I''m an orphan!'
∇
∇ Naming name
:Implements constructor
:Access public
'I was born with the name ',name
∇
:EndClass
p←⎕NEW Person 'Joe'
3↑p
Advanced properties#
You can also have a :property numbered
which acts like a normal property, but if you use indices to set or get, those functions are called with a namespace that has an Indexers
member to tell the function which elements are being asked for.
Remember the Shape
function of a property we mentioned briefly before? This means that a property can have any (pretend) shape. So when Get
or Set
are called, the argument has a member called IndexersSpecified
which is a Boolean vector indicating which dimensions are being addressed. You can use this, for example, to implement sparse arrays.
You can also have a :Property keyed
which instead of numeric indices can use any arrays as keys. It is then up to the Set
and Get
functions to handle these. Typically you’d want to use character vectors as keys. For such properties you must use indexing, as APL cannot know how many “elements” there are. You can use this to implement dictionary objects.