Code blocks = Lazy code evaluation

No need to worry about going "off topic" here
Forum rules
This forum is for general chit-chat, not necessarily APL-related. However, it's not for spam or for offensive or illegal comments.

Code blocks = Lazy code evaluation

Postby petermsiegel on Sun Aug 18, 2013 10:10 pm

Here are some thoughts for allowing a code block syntax, built on dfn syntax (and working similarly internally), that would also allow for lazy evaluation. This in turn would allow some nice if-then-else functions like Perl ?: syntax, as well as logical and and or, without using new symbols:

If we let {...} be a code block (distinguished from a d-function by not having top-level ⍵), we can have sequences like:
Code: Select all
   i← 0 ⋄ j←1
   nextI←{i+←1 ⋄ i}
   nextJ2←{j×←2 ⋄ j}


The basic idea is that the code block would only be executed when required-- that is whenever its values are required, e.g. during a numerical operation

Code: Select all
   a←nextI÷nextJ2      ⍝ 1÷2,   2÷4, ... 


or when peeking:
Code: Select all
    ⍴,{ ⎕←nextI ÷ nextJ2}
.3875
1                             ⍝ it has one item


This also allows a convention for lazy evaluation of boolean expressions, without fundamentally changing boolean definitions. E.g. b1 ∨ b2 can be defined as evaluating b2 and if true, leaving b1 unevaluated:

Code: Select all
 :if   {i ≤ 10}   ∨  { flagTrue}        ⍝   i≤10 not evaluated if flagTrue.   


Lazy evaluation could delay evaluation for any general structural operations that do not peer inside the block:

Code: Select all
5 + {⍳5}      ⍝ immediately evaluated to 6...10

  i234←{⎕←'i234 run' ⋄ ⍳2} {⍳3}  {⍳4}
  ⍴i234                                  ⍝ does not peer inside, since there are 3 blocks, each of which is a scalar "envelope"
3
  ⍴¨i234                                ⍝ has to peer inside, so lazy code blocks are evaluated in situ
i234 run
 2 3 4


Any operation that requires knowledge of internal structure or contents will force evaluation; this includes: depth ≡ catenate , and so on, to keep code blocks otherwise equivalent to their evaluated brethren:

Code: Select all
  first last ← 'John' 'Jones'
  name ← {first,' ',last}
  'My name is ',name
My name is John Jones

  last ← 'Smith'
  'His name is ',name
His name is John Smith

   {1, 3, 5}, {two ← 2 ⋄ 6 7 + ⍳two}, 11       ⍝ Catenation forces execution
1 3 5 7 9 11


We can force evaluation by a definition of monadic ⊢ to indicate it will evaluate its argument, without changing its shape...

Code: Select all
     ⊢ {first, ' ', last}
John Smith


Given this definition, code blocks can be passed to functions and can form the basis of if-then assignments. We can imagine a ⎕THEN function (choose an appropriate native symbol):

Code: Select all
       benefits ←   (total ≤ 100000) ⎕THEN { total × 1.1025} {total x 1.01}      ⍝ Choose the first block (1.1025) if the condition (total ≤ 100000) is true, else the second block... 


Or even:
Code: Select all
        benefits ← (1++/total > 100000 200000 ) ⎕SELECT  { total × 1.053} { total × 1.1025}  {total x 1.01}     ⍝ Select the first block if total ≤100000, second if ≤200000, else 3rd 


Note that this is the same as
Code: Select all
      benefits ←  ( 1++/total > 100000 200000 )   ⊃  { total × 1.053} { total × 1.1025}  {total x 1.01}            ⍝ Logical selections could be developed without a lot of extra syntactic sugar...


Because this approach fits in well with existing d-function syntax (having some superficial similarities to a 0-adic dfn), it should be easy to learn and provides needed extensions to APL semantics, without proliferation of :IF :ANDIF :ORIF types of structures. While I didn't show any sophisticated examples of vector-intensive logic, it would be easy to show without additional rules.
petermsiegel
 
Posts: 141
Joined: Thu Nov 11, 2010 11:04 pm

Re: Code blocks = Lazy code evaluation

Postby Phil Last on Mon Aug 19, 2013 12:19 pm

Although I shouldn't wholly disagree with the utility of such a construct, current Dyalog facilities largely render the detail of your proposition precluded and the substance redundant.

The expression
      r←{this conditional code}⍣(cond≡1)⊢default
runs the ⍺- and ⍵-free expression "this conditional code" if cond≡1 or returns default otherwise. The conditional expression may contain many potential errors that will never see the light of day so long as cond≢1. But although it contains no reference to args ⍺ and ⍵ it absolutely relies on the fact that APL will not run it until and unless it has at least a right argument.

So we have conditional code blocks already in the form of conventional dfns combined with ⍣ (or other operators) without the need for reversion to nilads which thankfully do not yet have a place in dfns.

See http://dfns.dyalog.com/n_and.htm for a solution (albeit left-to-right) of your "∧" and "∨" constructs.
User avatar
Phil Last
 
Posts: 628
Joined: Thu Jun 18, 2009 6:29 pm
Location: Wessex

Re: Code blocks = Lazy code evaluation

Postby petermsiegel on Wed Aug 21, 2013 2:56 am

Thanks. I'd read the posted material before and it is nicely done. I guess it's a question of elegance. Consider the wording (from the URL) "We have to resort to complex guards," or perhaps more seriously, the inelegance (and opacity even) of using dummy arguments to dfns, simply to signal to the interpreter to move from declaration to execution mode. And again, not to debate, but such a use of code blocks is all about code sequences common in other languages (a number of whom have syntactic mechanisms for delaying evaluation); despite the braces, they aren't really niladic functions, just take their space (and do so mnemonically), avoiding unnecessary symbols.

But then, to debate, look at all the code being developed with the :IF :AndIF types of code sequences and consider how inelegant they are-- scalar, bulky and often inadequate. Lazy (code) blocks, done right, can easily be combined with all kinds of non-scalar APL operations, including scans and the like, to make theoretically elegant and efficient code. Wouldn't it be nice if we had a better alternative to block :IF statements, esp. when we want to evaluate more than one option?

Ciao.
petermsiegel
 
Posts: 141
Joined: Thu Nov 11, 2010 11:04 pm

Re: Code blocks = Lazy code evaluation

Postby JohnS|Dyalog on Wed Aug 21, 2013 9:55 am

John's 2¢:

Your Code Blocks look useful, though I have a couple of reservations about the details.

We explored this area during the APL# research project in which we tried to merge dfns and tradfns into a single unified structure. The problem was with niladic "functions" (an oxymoron, in my opinion, like military "music").

I see Tradfns as being preferable for coding procedures (first do this, then do that, ...) and Dfns better for coding functions (mappings between, or pairings of, domain items with range items). So I would prefer if Code Blocks, which are procedures, didn't borrow Dfns' curly braces - perhaps we can think of a new kind of bracket. Unicode probably has many to choose from.

I'm iffy about using the absence of an ⍵ somewhere in the body of code to indicate that this is not a function. It's easy for the interpreter to distinguish but, with a sizeable function, harder for the human reader. I accept that D-ops already commit this sin; we try to help by syntax colouring the operator's containing braces but it's less than ideal.

Finally, I'm worried about the complexity of the rules for when to evaluate the body of the Code Block. I think you're suggesting the block be evaluated only when the calling environment needs to examine one of the items of its result, rather than treating it as a whole. So: naming, stranding, enclosing (?), passing-as-argument, ... would not evaluate, but: arithmetic, ravelling, reshaping, disclosing, ... would. I suppose this could be made to work but it makes me feel slightly giddy.
JohnS|Dyalog
 

Re: Code blocks = Lazy code evaluation

Postby Phil Last on Wed Aug 21, 2013 5:09 pm

Not 100% relevant but it shows another way to avoid
all the code being developed with the :IF :AndIF types of code sequences ... inelegant ... scalar, bulky and often inadequate.

The dfns ws contains operator - else - that permits a simple if-then-else condition:
      else←{⍺:⍺⍺ ⍵ ⋄ ⍵⍵ ⍵} ⍝ was typo - '⍵'s were '⍺'s /pl

It requires a left argument that is likely to be the result of a test on ⍵. We can name three functions: Test, True (that will be applied if Test returns true) and False (need I say?) all of which will take the same argument. Thus:
      res ← (Test arg) True else False arg

A much more expensive operator - cond - (not shown here) combines all three functions including the test as:
      res ← Test cond True cond False arg

Of course we can do all this with a simple guard:
      res ← {Test ⍵ : True ⍵ ⋄ False ⍵} arg

Of the three only the second using cond produces a tacit function (Test cond True cond False) that includes Test. The third requires the added braces around the entire expression to prevent exit from - and make the result available to - the calling function but it still requires the argument to be coded explicitly for each of the three functions.

DyalogAPL version 14 will include forks:
      (f0 f1 f2) rarg ←→ (f0 rarg) f1 (f2 rarg)

(there is a dyadic form which doesn't concern us here)

It turns out that operator else can be co-opted for a much more efficient tacit if-then-else condition as:
      res ← (Test True else False ⊢) arg

The binding is such that True and False combine with else to derive a function that becomes the middle tine of a fork. The left tine is Test and the right is just that - right ⊢. So both Test and ⊢ are applied to ⍵ and their results become ⍺ and ⍵ to True else False.

(Test True else False ⊢), being a function in its own right, can become operand to another operator or it can be assigned a name and re-used elsewhere.
User avatar
Phil Last
 
Posts: 628
Joined: Thu Jun 18, 2009 6:29 pm
Location: Wessex

Re: Code blocks = Lazy code evaluation

Postby petermsiegel on Mon Sep 02, 2013 9:14 pm

These approaches are all very elegant and useful. As pedagogical elements, e.g. teaching new users, we then need to communicate these approaches, rather than asking each (new) user to discover them (more or less on their own) by looking at code fragments. I use the concise if-then elements of dfns for, among other things, setting argument defaults: a← {0:: ⍵ ⋄ a} b ⍝ If a isn't already set, set a←b.

One nice thing about code blocks is that (as your examples show), we already are using dfns as if niladic choices; I am just proposing that we institutionalize such uses as primary, describe them systematically. and use their full power. Certainly, the scope challenges for using code blocks for lazy evaluation are serious, but (as you probably have seen in your own code, as I have) these problems exist already, esp. when using multiple threads and given the limited documentation on ∘←. (That said, the challenge isn't really the scope, but the fact that Dyalog APL uses two different scoping strategies-- for those with lots of programming language experience, this is not a problem, just something to note.) For lazy evaluation, we might look at other languages like Scheme or R, but relatively simple rules can work, such as evaluating them in their original context, including keeping referenced variables. (While I am being a tad too brief here, such scoping rules within a single thread are a lot easier than existing rules for objects created and accessed across threads).

Finally, when will we decide to make dfns first-class objects, allowing them to be passed to functions as if real objects, manipulated within arrays, and so on. Because we can determine when 1- and 2-adic dfns are called easily enough, there is no reason not to allow such use. A solution for traditional functions, especially niladic functions (procedures), would be to wrap them in code braces. If Block1 is one procedure and Block1Alt is an alternative, we could manage them in a straightforward way:
Code: Select all
Sequences←{Block1} {BlockAlt}  ...                      ⍝ Both are standard niladic functions
option← Get_Info_From_User ...                         ⍝ Make choice based on input from user
Call_Our_Big_Program option ⌷Sequences            ⍝ We Select Either Block1 or Block1Alt and pass it unexecuted to Call...
⍝  Or alternatively, we might simply "run" our choice here:
⊢option⌷Sequences                                             ⍝ Select and execute...


By the way, consider how R manages "default" arguments, allowing the user to delay evaluating one variable until others are set. Here we assume that F and C might be defined in terms of each other:
Code: Select all
⍝ In a function header, we accept two args:   type and value.
⍝ If type is F, value is in Fahrenheit.
⍝ If C, we replace the default

⍝ We define C once and for all here in terms of F. If value is not passed in Fahrenheit, we will expect a Centigrade Value
C←{(F-32)×5÷9}                      ⍝ Default C  (in the style of R, the definition is set, but is delayed until needed)
:If  type≡'C'      ⋄ C ← value      ⍝ Of course, once we have the If logic, we can define the formula here, but
                                    ⍝ in a long function, putting the defs together at the top is helpful
:elseif type≡'F' ⋄ F← value         ⍝ Other options might go in a different direction...
:endif

⍝ Now perform calculations based on C...
'Increasing temperature by 1 degree C:'
C+←1
...
     


On another day, we might talk about creating named function arguments, e.g. with a mechanism like this one, where dyadic → quotes its left argument (and passes names specially to functions):
Code: Select all
result ← TempCalc ( F → 59.5) + TempCalc( 22 ⊣   'Cent is default') - TempCalc( C → 122.3)

This would be enabled significantly by the availability of (lazy) code blocks.
petermsiegel
 
Posts: 141
Joined: Thu Nov 11, 2010 11:04 pm


Return to Chat

Who is online

Users browsing this forum: No registered users and 1 guest