Pervasion of Ambivalence in APL Functions

John 'Jake' JacobAPLLeave a Comment

Introduction

I was recently tasked with modifying a Dyalog class currently running under Dyalog 16 so that it would run under Dyalog 17.1. The class in question was used to serialize APL data into a JSON format and back again. The problem was that it made use of the two I-beams 7159 and 7160. These are deprecated in 17.1 so had to go.

Simply converting to use ⎕JSON was not an option. Historically the structure of the serialized data was in a different structure to that handled by ⎕JSON and not compatible with the legacy data. My first thought was roll back to the earlier version and run the test cases. All but one test passed. That test was one of two added when the last modification had been made. So I inspected the current code to see how it dealt with this test case.

The current solution had been implemented as a set of dfns all of which employed a common construct which immediately piqued my interest and that is the point of this article.



{⍺←⊣
      {⍺←⊣
         ...
      }
      ...
   }


This construct rang a bell and I remembered a late-night conversation with Phil Last at an Iverson College in Cambridge. I was a little vague on the details, so I asked Phil and got an explanation.

Elegance is not a high coding priority for me. But I think this is so elegant and also adds Robustness (which is one of my higher coding priorities) that it just had to be shared. So here follows Phil’s explanation.


Phil’s explanation

dfns are always ambivalent. Whether the arguments are referenced or not



      2{3}4
   3
      {3}4
   3
      2{⍵}4
   4
      {⍵}4
   4
      2{⍺}4
   2
      {⍺}4
   VALUE ERROR
      {⍺}4
      ^


But they are not proof against errors. An assignment to is conditional on the function's being called as a monad.



      2{⍺←3 ⋄ ⍺}4
   2
      {⍺←3 ⋄ ⍺}4
   3


Sometimes we want dfns to do more than just return a constant or one of the arguments.



      2{⍺÷⍵}4
   0.5
      {⍺÷⍵}4
   VALUE ERROR
      {⍺÷⍵}4
      ^
      2{⍺←3 ⋄ ⍺÷⍵}4
   0.5
      {⍺←3 ⋄ ⍺÷⍵}4
   0.75


If we supply the identity (if there is one) we can let it behave as the ambivalent primitive.



    2{⍺←1 ⋄ ⍺÷⍵}4
0.5
    {⍺←1 ⋄ ⍺÷⍵}4
0.25


Supplying instead actually makes it ambivalent.



      2{⍺←⊢ ⋄ ⍺÷⍵}4
   0.5
      {⍺←⊢ ⋄ ⍺÷⍵}4
   0.25
      2{⍺←⊢ ⋄ ⍺-⍵}4
   ¯2
      {⍺←⊢ ⋄ ⍺-⍵}4
   ¯4


The function to the right of ⍺ looks for a left argument. If ⍺ is an array then it is found one (2÷4, 2-4). But if ⍺ is the function acts monadically (÷4, -4) and returns the result that becomes the right argument to But is defined merely to return its right argument so it just passes on the result of the function as if is not there.

It is limited in that can only be the left argument wherever it is used.

We cannot do



      2{⍺←⊢ ⋄ ⍵÷⍺}4
   2
      {⍺←⊢ ⋄ ⍵÷⍺}4
   SYNTAX ERROR
      {⍺←⊢ ⋄ ⍵÷⍺}4
      ^


There is history to this

There is a long history to this. When Phil first showed this to John Scholes, John was pleased that it worked. Phil believed he had not actually coded it to work like that. Just hadn't got round to coding it to forbid it.

Whilst I was discussing this technique with the team here at Optima, James Heslip drew my attention to the following dfn he had seen demonstrated by Adám Brudzewsky.


 
perv←{⍺←⊢               ⍝ Scalar pervasion
     1=≡⍺ ⍵ ⍵:⍺ ⍺⍺ ⍵     ⍝ (⍺ and) ⍵ depth 0: operand fn application
     ⍺ ∇¨⍵      ⍝ (⍺ or) ⍵ deeper: recursive traversal.
 }


This example is drawn from dfns.dws written by John Scholes.

Quoting Phil again on a follow up comment to the explanation above.

“John wrote dfns expecting edited ⍺ to be assigned an array but he loved the fact that this technique worked as well. But in my previous explanation I'd forgotten that ⍺←⊢ wasn't at first available because and were only implemented several years after dfns in v13.0. But ⍺←{⍵} worked for the intervening years. I think Dan was the next to pick it up after John.”

But the story goes back further. Phil had shown John Scholes a function that he wrote back in '89:


 
        ∇ AMBIVALENT AMBIVALENT
    [1] AMBIVALENT←⎕FX('R←',AMBIVALENT,' B')'R←B'
        ∇


This generalised a technique Phil had been using since '83 or '84 that allows F00 to be called ambivalently.


 
        ∇ R←A F00 B
    [1] 0 0⍴⎕FX 1 5⍴'R←A R'
    [2] ...
        ∇


Now I vaguely remember something of this technique from my time working with Phil at BUPA; that was in APL2 or possibly VSAPL; though way back then I was a newbie APLer and I am afraid it went right over my head. Now I know I can be a little slow on the uptake sometimes, but thirty-seven years is a bit much. But I’m glad that I finally get it and I was not expecting that when I started unpicking that JSON class.

Never too late to learn I guess!


About the Author

A picture of John Jacob the author of this blog

John 'Jake' Jacob

Technical Lead


Jake’s working life began as a Field Biologist for Wandsworth Borough Council; collecting, identifying and recording animal and plant life in parks, commons and other habitats; the data was used by the Borough and interested parties such as Friends of the Earth. Jake also used this material to write nature guides in Putney and Wimbledon. More about Jake.


More from Jake



Other Posts