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'-'

    Upper1⎕C
    Lower⎕C

    :Property Name
    :Access Public
         textGet
          :If '-'name
              text'I don''t have a name!'
          :Else
              text'Hi, my name is ',name,'!'
          :EndIf
        
         Set text
          name(Upper 1text.NewValue),(Lower 1text.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
-
I don't have a name!
Hi, my name is Anton!
Anton

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 age0
    :Field name'-'

    :Property Age
    :Access Public
         numget
          numage
        
    :EndProperty

     Grow amount
      :Access Public
      age+amount
    

    Upper1⎕C
    Lower⎕C

    :Property Default Name
    :Access Public
         textGet
          :If '-'name
              text'I don''t have a name!'
          :Else
              text'Hi, my name is ',name,'!'
          :EndIf
        
         Set text
          name(Uppertext.NewValue),Lower 1text.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
0
3
4
Hi, my name is Bruno!

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 ageVal0

    :Property height,weight,age
    :Access public
         rGet 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 ageVal0

     Birth(h w)
      :Access public
      :Implements constructor
      (heightVal weightVal)h w
    

    :Property height,weight,age
    :Access public
         rGet 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
         bmiGet
          bmi0.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
3
4
15

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 
#.[Namespace]

However, the system function ⎕DF (Display Form) allows you to change this to any character array:

ns⎕NS 
ns.⎕DF 2 2'yo'
ns
yo yo

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'
    

    Upper1⎕C
    Lower⎕C

    :Property Name
    :Access Public
         textGet
          :If 0=⎕NC'name'
              text'I don''t have a name!'
          :Else
              text'Hi, my name is ',name,'!'
          :EndIf
        
         Set text
          name(Uppertext.NewValue),Lower 1text.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
baby
I don't have a name!
Hi, my name is Anton!
Anton

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⎕Cp          ⍝ ⍕p is the actual ⎕DF: we can for example upcase it
Anton
9
ANTON

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?

103 1 4   ⍝ Overtake a list of ints pads with 0
3 1 4 0 0 0 0 0 0 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'
3p
I was born with the name Joe
I'm an orphan! I'm an orphan! #.[Person] #.[Person] #.[Person]

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.