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
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,
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
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
In the code abobe,
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.
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
]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
]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
The normal display of an object is with a namespace path and object name or class name/”namespace” in brackets. Not very useful:
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
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
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
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.
Shape function of a property we mentioned briefly before? This means that a property can have any (pretend) shape. So when
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
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.