Type checking discussion, what's your solution? (mini rant)

APL-related discussions - a stream of APL consciousness.
Not sure where to start a discussion ? Here's the place to be
Forum rules
This forum is for discussing APL-related issues. If you think that the subject is off-topic, then the Chat forum is probably a better place for your thoughts !

Type checking discussion, what's your solution? (mini rant)

Postby norbertjurkiewicz84 on Thu Nov 16, 2017 4:37 pm

An open discussion on type checking. Another issue with a dozen solutions and no best-practices. Reminds me of defaulting a shy left argument of a trad-fn.

Writing something to be consumed by different people and applications needs strong argument checking with descriptive error messages. In C#, I can define a Boolean Property and the language will not allow anything else in. It'll even give a helpful Exception without doing anything checking within the app.

A Dyalog Class Property works like anything else, it lets anything in. Makes sense and it's super powerful because you can have different inputs convert to your required data.

That's great until you want to write a utility class and avoid a ton of boilerplate copy-pasted type checking code in Property setters.

For example:

      :property DisplayLogWindow
:access public instance
∇ r←get
r←_displayLog


∇ set arg
_displayLog←arg.NewValue

:endproperty


The app expects a boolean 1 or 0 but maybe a JSON true and false should also be accepted... This can make for nice HTTP API's.

The simplest of Properties

Code: Select all
    :property DisplayLogWindow
    :access public instance
        ∇ r←get
          r←_displayLog
        ∇

        ∇ set arg
          _displayLog←arg.NewValue
        ∇
    :endproperty


We get something like this

Code: Select all
   
h5.DisplayLogWindow←0
h5.DisplayLogWindow
 0
h5.DisplayLogWindow←1
h5.DisplayLogWindow
 1
h5.DisplayLogWindow ←  ⊂'true'
h5.DisplayLogWindow
 true
⍝ but I want the result to be 1.
h5.DisplayLogWindow←2
h5.DisplayLogWindow
 2
⍝Not valid.



We can smarten up the setter by checking the argument and giving a context-sensitive error.

      :property DisplayLogWindow
:access public instance
∇ r←get
r←_displayLog


∇ set arg;v;ok;val
v←arg.NewValue

:If ~{(0≡⍵)∨1≡⍵}v ⍝ numeric or boolean true or false? Do nothing
:If 3=80|⎕DR v ⍝ check in number outside of boolean range
⎕SIGNAL⊂('EN' 11)('Message' 'Number is not a Boolean value.')
:ElseIf 0=80|⎕DR v ⍝ simple true or false?
val←(819⌶)v
:If ~(⊂val)∊'true' 'false'
⎕SIGNAL⊂('EN' 11)('Message' 'Invalid text representation of a Boolean value. Must be ''true'' or ''false''.')
:EndIf
v←val≡'true'
:ElseIf {(326=⎕DR ⍵)∧1=≢⍵}v ⍝ JSON true or false?
:If ~v∊'true' 'false'
⎕SIGNAL⊂('EN' 11)('Message' 'Invalid nested representation of a Boolean value. Must be ⊂''true'' or ⊂''false''.')
:EndIf
v←v≡⊂'true'
:Else
⎕SIGNAL⊂('EN' 11)('Message' 'Value is not a Boolean.')
:EndIf
:EndIf

_displayLog←v

:endproperty


The best thing I can do here is make the checker its own function and create some utilities.

      :class TestClass

:property DisplayLogWindow
:access public instance
∇ r←get
r←_displayLog


∇ set arg
_displayLog←isBoolean arg.NewValue

:endproperty

isBoolean←{
⍝ Checks if right argument is a valid numeric, text or JSON boolean value.
⍝ ⍵ - Value to check
⍝ ← - 0 or 1 Boolean from arguments.
⎕ML←3
tf←'true' 'false'
(0≡⍵)∨1≡⍵:⍵ ⍝ numeric or boolean true or false? Do nothing
3=80|⎕DR ⍵:⎕SIGNAL errTable 0 ⍝ check in number outside of boolean range
0=80|⎕DR ⍵:{val←(819⌶)⍵ ⋄ (⊂val)∊tf:val≡'true' ⋄ ⎕SIGNAL errTable 1}⍵ ⍝ simple true or false?
{(326=⎕DR ⍵)∧1=≢⍵}⍵:{⍵∊tf:⍵≡⊂'true' ⋄ ⎕SIGNAL errTable 2}⍵ ⍝ JSON true or false?
⎕SIGNAL errTable 3
}

errTable←{
EN←('EN' 11)
⍵=0:⊂EN('Message' 'Number is not a Boolean value.')
⍵=1:⊂EN('Message' 'Invalid text representation of a Boolean value. Must be ''true'' or ''false''.')
⍵=2:⊂EN('Message' 'Invalid nested representation of a Boolean value. Must be ⊂''true'' or ⊂''false''.')
⍵=3:⊂EN('Message' 'Value is not a Boolean.')
⊂EN('Message' '')
}

:EndClass



Wouldn't it be nice if the Class itself inherited an Object with these utilities built in...

      :Namespace MyProject

:class MyClass:Object

:property DisplayLogWindow
:access public instance
∇ r←get
r←_displayLog


∇ set arg
_displayLog←IsBoolean arg.NewValue

:endproperty

:endClass

:class Object

∇ Z←IsBoolean arg
:Access public shared
Z←{
⍝ Checks if right argument is a valid numeric, text or JSON boolean value.
⍝ ⍵ - Value to check
⍝ ← - 0 or 1 Boolean from arguments.
⎕ML←3
tf←'true' 'false'
(0≡⍵)∨1≡⍵:⍵ ⍝ numeric or boolean true or false? Do nothing
3=80|⎕DR ⍵:⎕SIGNAL errTable 0 ⍝ check in number outside of boolean range
0=80|⎕DR ⍵:{val←(819⌶)⍵ ⋄ (⊂val)∊tf:val≡'true' ⋄ ⎕SIGNAL errTable 1}⍵ ⍝ simple true or false?
{(326=⎕DR ⍵)∧1=≢⍵}⍵:{⍵∊tf:⍵≡⊂'true' ⋄ ⎕SIGNAL errTable 2}⍵ ⍝ JSON true or false?
⎕SIGNAL errTable 3
}arg


errTable←{
EN←('EN' 11)
⍵=0:⊂EN('Message' 'Number is not a Boolean value.')
⍵=1:⊂EN('Message' 'Invalid text representation of a Boolean value. Must be ''true'' or ''false''.')
⍵=2:⊂EN('Message' 'Invalid nested representation of a Boolean value. Must be ⊂''true'' or ⊂''false''.')
⍵=3:⊂EN('Message' 'Value is not a Boolean.')
⊂EN('Message' '')
}

:endclass

:EndNamespace



This is starting to take shape! I now have reusable code that will be available in all my classes, as long as I set all parent classes to inherit from Object.

Maybe we should have classes inherit from a default object without defining it?

Time for a tested open source project that everyone can take advantage of?
User avatar
norbertjurkiewicz84
 
Posts: 62
Joined: Mon Nov 01, 2010 7:26 pm

Return to APL Chat

Who is online

Users browsing this forum: No registered users and 1 guest