Variant ⍠

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 !

Variant ⍠

Postby petermsiegel on Mon May 18, 2020 11:55 pm

Any thought on allowing for variant ⍠ to be used in user functions?

Seems like users develop as many functions that would benefit from such customization.

It would seem this could also be an impetus for explicitly defining variant syntax a bit formally.

Also, any thought on its use with things like ⍳ to document index origin and such, e.g. via (⍳⍠0) for
{⍺←⊢ ⋄ ⎕IO←0 ⋄ ⍺⍳⍵}
petermsiegel
 
Posts: 83
Joined: Thu Nov 11, 2010 11:04 pm

Re: Variant ⍠

Postby Morten|Dyalog on Tue May 19, 2020 7:58 am

Hi Peter!

As I remember it, we came very close to implementing (⍳⍠1) when we did the work, in fact I had to try it to be sure whether that worked or not. It would be nice to return to variant and do that, I agree.

Variant on user-defined functions was/is further off. At the moment, I'm hoping that a literal namespace notation will soon provide much of what I suspect people actually want variant for with defined functions. Hopefully you will be able to write something along the lines of:

MyFunction (name:'Hello' ⋄ life:42)

... which will pass a namespace with two variables in it to MyFunction. The exact syntax is still being debated.
User avatar
Morten|Dyalog
 
Posts: 388
Joined: Tue Sep 09, 2008 3:52 pm

Re: Variant ⍠

Postby StefanoLanzavecchia on Tue May 19, 2020 9:32 am

We have built our own operators PLUS.opt (and PLUS.unopt to actually parse what gets given to a user defined function):

Code: Select all
     ∇ {r}←{x}(f opt o)y;n
[1]   ⍝ ⎕OPT for us who like to live in a complex world
[2]    :If ~0∊⍴o
[3]        n←⎕NEW variant(y o)
[4]    :Else
[5]        n←y
[6]    :EndIf
[7]
[8]    :If 0=⎕NC'x'
[9]        x←⊢
[10]   :EndIf
[11]  ⍝ n.qopt←{⍺←⊢ ⋄ 1:_←⍺(⍺⍺ ⎕OPT opts)rargs}
[12]   r←x f n
[13]  ⍝∘ (c)APLIT W PLU stf 22/10/2018 19:24                23172 D95
     ∇


Code: Select all
     ∇ (rargs opts)←n unopt t;v;x;r
[1]    :If 9.2=⎕NC⊂,'n'
[2]    :AndIf #.PLUS.variant≡⊃⊃⎕CLASS n
[3]        rargs opts←n.(rargs opts)
[4]    :Else
[5]        opts←⍬
[6]        rargs←n
[7]    :EndIf
[8]    :If 9=⎕NC't'
[9]        r←⎕NS''
[10]       :If 0<≢opts
[11]           :If #.IsChar⊃opts
[12]               opts←,⊂opts
[13]           :EndIf
[14]
[15]           :For (x v) :In opts
[16]               :If 0=t.⎕NC x
[17]                   ⎕←'⍝ WARNING: unknown variant: ',x
[18]               :Else
[19]                   x r.{⍎⍺,'←⍵'}v
[20]               :EndIf
[21]           :EndFor
[22]
[23]       :EndIf
[24]       opts←t nsb r
[25]   :EndIf
[26]  ⍝∘ (c)APLIT W PLU stf 01/04/2020 12:32                 5336 D95
     ∇


They depend on a class:
Code: Select all
:Class variant                                                 
                                                               
    :field public instance rargs                               
    :field public instance opts                               
                                                               
    ∇ make(_rargs _opts)                                       
      :Access public instance                                 
      :Implements constructor                                 
      rargs opts←_rargs _opts                                 
    ∇                                                         
                                                               
:EndClass                                                     
⍝∘ (c)APLIT W PLU stf 17/03/2020 12:21                24509 D95


Other useful code. "nsb" takes a function which uses a template to build a namespace:
Code: Select all
     ∇ nsb←{
[1]    ⍝ build a namespace from a defining function
[2]
[3]        b_←1 #.⎕FIX{
[4]            first←⊃⍵
[5]            last←⊃⌽⍵
[6]            middle←¯1↓1↓⍵
[7]            f←first≡last
[8]            spl←⍳⍨(↑{⍺ ⍵}↓)⊢
[9]            a←(-f)↓2⊃'{'spl first
[10]           b←(~f)/¯1↓last
[11]           (':namespace'a),middle,b':endnamespace'
[12]       }
[13]
[14]       ⍵{
[15]           9≠⎕NC'⍺':⍵._n
[16]           _r←⍵._n
[17]           _r⊣'_r'⎕NS ⍺
[18]       }{
[19]           fns←⍵.⎕NL ¯3
[20]           _←⍵{
[21]               ns←b_ ⍺.⎕NR ⍵
[22]               ⍵ ⍺.{⍎'⎕ex ⍺⋄',⍺,'←⍵'}ns
[23]           }¨⍣(×≢fns)⊢fns
[24]           ⍵⊣(∇ ⍵⍎⊢)¨⍣(×≢fns)⊢fns
[25]       }⍺⍺{
[26]           _n←⍺⍺
[27]           r←⎕NS''
[28]           r._n←_n
[29]           r
[30]       }''
[31]  ⍝∘ (c)APLIT W PLU stf 01/04/2020 12:30                11876 D95
[32]   }
     ∇


"ns2opt" transforms a namespace into a list of "name/value" pairs:
Code: Select all
     ∇ ns2opt←{
[1]        ⍺←⍬
[2]        r←,#.ùlshub ⍺
[3]        l←(⍵.⎕NL-2)~r
[4]        0∊⍴l:⍬
[5]        ⍵.{⍵(⍎⍵)}¨l
[6]   ⍝∘ (c)APLIT W PLU stf 22/10/2018 19:00                 7831 D95
[7]    }
     ∇


And are used like so:
Code: Select all
    #.PLUS.nexists #.PLUS.opt ('RetryOnError' 1)⊢'test.txt'


where #.PLUS.nexists looks like this:
Code: Select all
     ∇ R←nexists y;rt;o;f;op;X
[1]    y o←y unopt{RetryOnError←0 ⋄ ExtendedNexists←0}nsb''
[2]    f←#.IsChar y
[3]    y←#.DoSUBST¨,#.ùlshub y
[4]    :If 0<o.RetryOnError
[5]        op←⎕NEXISTS fretry
[6]    :Else
[7]        op←⎕NEXISTS dfs
[8]    :EndIf
[9]
[10]   :Trap 0 1000
[11]       R←(op opt('RetryOnError' 'ExtendedNexists'ns2opt o))y
[12]       :If o.ExtendedNexists
[13]           :If 0=#.⎕NC'mDFSActive'
[14]           :OrIf 0=#.mDFSActive
[15]               :If 1∊R        ⍝ and 2 for a directory the ExtendedExists flag determines if we process further
[16]                   R+←R\1=1 ninfo R/y
[17]               :EndIf
[18]           :EndIf
[19]       :Else
[20]           R←0≠R
[21]       :EndIf
[22]       R←⊃(⍣f)R
[23]   :CaseList 19 28
[24]         ⍝ ∆DMX was set by fretry
[25]       resignal ⎕DMX
[26]   :Else
[27]       X←rethrow SaveDMX ⎕DMX
[28]       logerr
[29]
[30]       ⎕SIGNAL X
[31]
[32]   :EndTrap
[33]  ⍝∘ (c)APLIT W PLU stf 01/04/2020 12:33                19489 D95
     ∇


Notice in "nexists" the call to "unopt" to get the variant options which appear in namespace o. Also the call to "ns2opt" which transforms the namespace back into something that can be used as an argument to []OPT. Operator "dfs" (which operator "fretry" calls anyway" looks like this:
Code: Select all
[1]    b o←b unopt ⎕NULL
[2]    :If 0=⎕NC'a' ⋄ a←⊢ ⋄ :EndIf
[3]    :If 0≠#.⎕NC'mDFSActive'
[4]    :AndIf #.mDFSActive
[5]        c←1↓⊃⎕CR'fn'
[6]        _t←⎕AI[3]
[7]        :If ~0∊⍴o
[8]            r←(#._Q.⍎c)o #._Q.VARIANT b
[9]            l←r'←'(c b)
[10]       :Else
[11]           r←a(#._Q.⍎c)b
[12]           l←r'←'(a c b)
[13]       :EndIf
[14]       _t←⎕AI[3]-_t
[15]       _t dfslog l
[16]   :Else
[17]       :If ~0∊⍴o
[18]           r←a(fn ⎕OPT o)b
[19]       :Else
[20]           r←a fn b
[21]       :EndIf
[22]   :EndIf
[23]  ⍝∘ (c)APLIT W PLU stf 28/10/2018 17:42                15860 D95
     ∇

You can see that eventually we are back to using []OPT for anything we could not interpret ourselves. So, RetryOnError is a custom variant, which we interpret in the actual implementation of PLUS.nexists. Other variants are passed straight to []NEXISTS via []OPT.
Last edited by StefanoLanzavecchia on Tue May 19, 2020 5:17 pm, edited 2 times in total.
User avatar
StefanoLanzavecchia
 
Posts: 80
Joined: Fri Oct 03, 2008 9:37 am

Re: Variant ⍠

Postby Roger|Dyalog on Tue May 19, 2020 4:05 pm

Morten|Dyalog wrote:As I remember it, we came very close to implementing (⍳⍠1) when we did the work, in fact I had to try it to be sure whether that worked or not. It would be nice to return to variant and do that, I agree.

I believe allowing a particular unnamed right operand as the default variant has complications. For dyadic ⍳ in particular, {⎕ct←0 ⋄ ⍺⍳⍵} is (to me) a more useful option. Besides, never forget,

⎕io delenda est!

More generally, I don’t see that there is that much to be gained by being able to say

      ⍳⍠0 or ⍳⍠'⎕io' 0    instead of   {⎕io←0 ⋄ ⍺⍳⍵} 
⍳⍠'⎕io' 0 '⎕ct' 0 instead of {⎕io←0 ⋄ ⎕ct←0 ⋄ ⍺⍳⍵}

My 2 cents.
Roger|Dyalog
 
Posts: 208
Joined: Thu Jul 28, 2011 10:53 am

Re: Variant ⍠

Postby StefanoLanzavecchia on Tue May 19, 2020 5:04 pm

Alternative:
Code: Select all
      io0←⎕ns ''
      io0.⎕io←0
      1 2 3 io0.⍳ 2
1


You would create your special use namespaces at app startup and then use them freely. This only works for primitives, though, not tradfn or dfn.

In fact, the latter are sneakier. Proof:
Code: Select all
      loo←{ ⎕←'⎕ct: ',⍕⎕ct ⋄ ⍺⍳⍵ }
      1 2 3{ ⎕ct←0 ⋄ ⍺ loo ⍵ } 2
⎕ct: 1E¯14
2


While coherent with the evaluation rules imposed by dfn, this is still somewhat surprising and will bite you at least once. Now, "loo" is very trivial, but if instead of a cover to the primitive "iota" it implemented, let's say, a progressive iota, then it would make more sense as a named dfn. Alas, the local []CT setting would still not be transferred to it. It would if loo was a tradfn instead of being a dfn. Proof:

Code: Select all
     ∇ r←x loot y
[1]  ⍝ WARNING: do not, I repeat, DO NOT turn this into a dfn, not matter how tempted you may be!
[2]  ⎕←'⎕ct: ',⍕⎕CT
[3]  r←x⍳y
     ∇

     1 2 3{ ⎕ct←0 ⋄ ⍺ loot ⍵ } 2
⎕ct: 0
2


This time it does what we want. But notice the big warning...
User avatar
StefanoLanzavecchia
 
Posts: 80
Joined: Fri Oct 03, 2008 9:37 am

Re: Variant ⍠

Postby petermsiegel on Tue May 19, 2020 8:07 pm

Thanks, Stefano, for sharing your strategy. It seems like a very good and flexible approach (even with Roger's caveats).

Roger-- Regarding your comment that the DFN approach a { ⎕IO←... ⋄ ⎕CT←... ⋄ a ⍳ w etc.} w seems just as good as (or even better than) using variant. Indeed for larger objects, the overhead of the DFN is quite small, though for lots of smaller calculations, it can add a lot, but I still grant your implicit point-- not likely a big impact on real programs.

That said, ought we not to prefer constrained constructions where the goal is to understand user intent. You can put ANYTHING between braces (and it takes a bit of time to parse the user's intent), but variant constrains what the user can assert.

In addition, an elegant opportunity of using variant is that it can help an optimizer or JIT COMPILER to act, by knowing what the user wishes to assert or guarantee. I.e. I could see various ways to provide compiler hints that are fundamentally good (and concise) documentation as well. [Afterthought: you can do this with ad hoc dfns, but it's more work and even for the current interpreter, idioms must be carefully written to be recognized. Variant expressions, with constant values, are trivial to recognize].

Note that Dyalog now has one of the new composition operators that can be used to guarantee a function versus operator use of / and ⌿. That is, it seems that Dyalog is already creating constructions which can as a side effect be useful for optimization because it concisely constrains valid use.

In any case, I think ⍠ can be used concisely and elegantly to assert the intent of the user with respect to ⎕CT, ⎕IO, ⎕ML, etc., thus aiding optimization.
petermsiegel
 
Posts: 83
Joined: Thu Nov 11, 2010 11:04 pm


Return to APL Chat

Who is online

Users browsing this forum: No registered users and 1 guest