What If ... ?
A recent post in comp-lang-apl posed the question: how do you process "conditionals" in APL?
In a scalar language this wouldn't be an ambiguous question.
In APL we might differentiate between program logic & what I will call arithmetic logic.
Program logic
At the very top level of a system we might want to decide between sub-systems according to some user input. This is likely to be some form of case statement however implemented.
Before the '80s this would be done either as a set of branches (→) to other lines in the same program or by executing (⍎) one of a number of alternative program statements instantiated as text strings.
Since the introduction of defined operators and the generalisation of primitive operators to defined operands in the early '80s it has been possible conditionally to run a function without recourse to either of the former, now largely deprecated, methods (→) and (⍎).
This possibility went almost unnoticed for the few years between then and the importation into APL of more traditional programming control structures by almost all vendors from the mid '80s onwards.
So now there are twenty ways to make the same choice and as we progress down the program tree we might easily encounter every one of them depending by whom and when that particular part of the system was implemented.
Any degree of complexity of program logic can be built upon a simple conditional clause where according to the result of one function we either run another or not.
In infix APL we might write:
r←f01 if f00 w ⍝ in which "if" is an operator - see Appendices for listings.
or using control structures:
r←w ⋄ :If f00 w ⋄ r←f01 w ⋄ :End
This is essentially all we need; a pile of these will do anything; but elaborations abound.
Two alternatives can be provided rather than a single one or nothing:
r←w ⋄ :If f00 w ⋄ r←f01 w ⋄ :Else ⋄ r←f02 w ⋄ :End
or in APL:
r←f00 then f01 else f02 w
Or many:
:Select f00 w :Case 1 ⋄ r←f01 w :Case 2 ⋄ r←f02 w :Case 3 ⋄ r←f03 w :Case 4 ⋄ r←f04 w :Else ⋄ r←w :End
which in infix notation reduces to:
(1 2 3 4=f00 w)f01 or f02 or f03 or f04 w
or
((f00 w),4)f01 in f02 in f03 in f04 w
Complexity can be added with Andif, Orif, Elseif or controlled iteration can be made explicit:
r←w ⋄ :While f00 r ⋄ r←f01 r ⋄ End
r←w ⋄ :Repeat ⋄ r←f01 r ⋄ :Until f00 r
or simply
r←f01 while f00 w
r←f01 until f00 w
Arithmetic logic
APL processes arrays. We can do conditional processing in such a way that a function is applied only to particular items in an array according to a test on all the items or different functions are applied to different subsets of items.
As a simple example that will be readily understood because most APLers will already have done it let's say we want to divide array (a) by array (w), giving new array (r) but we know that (w) may contain zeros.
It would be no good merely to add one to (w) to avoid the domain error and hope to adjust the result later; for one thing it may contain negative ones!
So we use APL's ability to do arithmetic with truth values and add the condition itself:
a÷w+w=0
so the divisors are non-zero (w) or 1 where (w=0)
so the result is either (a) ÷ non-zero (w) or (a) itself where (w=0)
If we keep the result of the condition:
a÷w+z←w=0
we can re-use it to force whatever result we want on the aberrant values:
(z×⌊/⍳0)+(~z)×a÷w+z←w=0
Let's stick with zero.
(~z)×a÷w+z←w=0
Another question arises: do we actually want to divide (a) by (w)?
Perhaps depending on another condition (d) we might want to decide whether to divide (d=1) or to multiply (d=0) as we might in calculating cross currency rates for instance. We could use a control structure for this:
:If d ⋄ r←(~z)×a÷w+z←w=0 ⋄ :Else ⋄ r←⍺×⍵ ⋄ :End
This might work if (d) is scalar. But what if (d) were a conforming array & differently 0 or 1 for each item.
":If d" won't work but we can proceed with arithmetic logic:
(~z)×a×(w+z←w=0)*¯1*d
Raising ¯1 to the dth power is either +1 (d=0) or ¯1 (d=1)
Raising (w) to the +1 or ¯1 power is itself or its reciprocal
Multiplying by (w) or its reciprocal is multiplying or dividing by (w).
Even if (d) is scalar, by using the control structure we introduce four extra program statements, where we need only one, and a dependency between two of them which must be maintained in all situations where the requirement might change.
In my experience it's quite unusual to find an arithmetic problem insuffiently mutable that we can't include conditional processing within it.
Even when there are such it's usually an easy solution to use indexed or selective assignment or manipulation. In the following assume (t) is a test that returns 1 where items of array (w) are in the domain of function (f):
w[x]←f w[x←(,⍳⍴w)/⍨,t w]
or
(x/,w)←f(x←,t w)/,w
or
w←(⍴w)⍴x⊖(,w),[⎕IO-0.1]x\f(x←t,w)/,w
at least one of which is likely to work on most systems.
Or we can encapsulate similar techniques in a defined operator to produce:
w ← f where t w
Appendix i
Conditional dynamic operators for Dyalog APL V10 and up - using ambivalent deriving commute
if←{ ⊃{⍵}∘({⍵}∘⍺⍺)/(1∩⍵⍵{⍵}⍨⍵),⊂⍵ } then←{ ⍺←0 (⍺=1):⍺⍺{⍵}⍨⍵ (⍺=2):⍵⍵{⍵}⍨⍵ (1 ⍺⍺ ⍵):2 ⍺⍺ ⍵ ⍵⍵{⍵}⍨⍵ } else←{ ⍺←0 ⍺(⍺⍺ then ⍵⍵)⍵ } or←{ ~1∊⍺:⍵ </⍺:⍵⍵{⍵}⍨⍵ 2=⍴⍺:⍺⍺{⍵}⍨⍵ (¯1↓⍺)⍺⍺ ⍵ } 1 0 0 0 ÷ or ⍟ or ○ or * 3 0.33333333333333 0 1 0 0 ÷ or ⍟ or ○ or * 3 1.0986122886681 0 0 1 0 ÷ or ⍟ or ○ or * 3 9.4247779607694 0 0 0 1 ÷ or ⍟ or ○ or * 3 20.085536923188 0 0 0 0 ÷ or ⍟ or ○ or * 3 3 in←{ (>/⍺)∨0∊⍺:⍵ =/⍺:⍵⍵{⍵}⍨⍵ ⍺≡1 2:⍺⍺{⍵}⍨⍵ (⍺-0 1)⍺⍺ ⍵ } 1 4 ÷ in ⍟ in ○ in * 3 0.33333333333333 2 4 ÷ in ⍟ in ○ in * 3 1.0986122886681 3 4 ÷ in ⍟ in ○ in * 3 9.4247779607694 4 4 ÷ in ⍟ in ○ in * 3 20.085536923188 0 4 ÷ in ⍟ in ○ in * 3 3 while←{ ⊃{⍵}∘∇∘⍺⍺/(1∩⍵⍵ ⍵),⊂⍵ } until←{ ⊃∇{{⍵}∘⍺⍺/(1~⍵⍵ ⍵),⊂⍵}⍵⍵ ⍺⍺ ⍵ } where←{ (⍴⍵)⍴⍺⍺{⍵ ⍺⍺{⍵⊖⍺,[⎕IO-0.1]⍵\⍺⍺ ⍵/⍺}⍵⍵ ⍵}⍵⍵,⍵ }
Appendix ii
Conditional operators for APL2 and compatibles - no branch (→), no execute (⍎), require braced left arg for Dyalog, tested on APL2 and Dyalog.
∇ r←(f if g)w [1] r←⊃dex jn(f jn lev)/(⍳1∊g lev w),⊂w ∇ ∇ r←a(f then g)w;b [1] 0 0⍴⎕FX 2 4⍴'r←a r←0 ' [2] b←1∊1 jn f if(a=0)dex w [3] r←2 jn f if(b∧a=0)dex w [4] r←g jn lev if(b<a=0)dex r [5] r←f jn lev if(a=1)dex r [6] r←g jn lev if(a=2)dex r ∇ ∇ r←a(f else g)w [1] 0 0⍴⎕FX 1 5⍴'r←a r' [2] r←a(f then g)w ∇ ∇ r←a lev w [1] 0 0⍴⎕FX 1 5⍴'r←a r' [2] r←a dex swap w ∇ ∇ r←a dex w [1] r←w ∇ ∇ r←a(f swap)w [1] 0 0⍴⎕FX 2 4⍴'r←a r←w ' [2] r←w f a ∇ ∇ r←a(f jn g)w [1] 0 0⍴⎕FX 1 5⍴'r←a r' [2] r←a f g w ∇ ∇ r←a(f or g)w;z;x;c;v [1] z←~1∊a [2] x←z<</a [3] c←z<x<2=⍴a [4] v←z<x⍱c [5] r←(g jn lev)if x dex(f jn lev)if c dex((¯1↓a)jn f)if v dex w ∇ ∇ r←a(f in g)w;z;x;c;v [1] z←(>/a)∨0∊a [2] x←z<=/a [3] c←z<x<a≡1 2 [4] v←z<x⍱c [5] r←(g jn lev)if x dex(f jn lev)if c dex((a-0 1)jn f)if v dex w ∇ ∇ r←(f while g)w [1] r←⊃dex jn(f while g)jn f/(⍳g w),⊂w ∇ ∇ r←(f until g)w [1] r←⊃dex jn(f until g)/(1~g r),⊂r←f w ∇ ∇ r←(f where g)w;b;v [1] v←,w [2] b←g v [3] r←(⍴w)⍴b⊖v,[⎕IO-0.1]b\f b/v ∇
-- PhilLast 2009-06-27 12:54:21