User:Cameron Chandoke/Functional Control Flow
This page assumes you are familiar with some J jargon: verbs, adverbs, conjunctions, tacit and explicit.
Introduction / Motivation
Each of J's control words (e.g. while.
, for.
, case.
, try.
) has one or more straightforward verb/modifier equivalents, which this article explores.
The advantage of control-flow verbs/adverbs/conjunctions over control words, besides their being much shorter, is that they are phrases (parts of a sentence) rather than sentences themselves, and can thus be made part of a longer phrase or sentence without requiring multiple sentences. In other words, they are more easily composed together. Hence they fit better with J's function-level style of programming.
In short:
- Fold (
F.
) and Terminate Fold (Z:
) providewhile.
/whilest.
,break.
, andcontinue.
- Power of Verb (
^:
) providesif.
andfor.
- Agent (
@.
) provideselse.
/elseif.
andselect.
/case.
/fcase.
as well as interval-based switching - a user-defined Cond adverb enables visual parsing of many-branch if-else statements
- nested if-else clauses can be flattened by distributing their predicates' logical dependencies, enabling processing by Cond
- Adverse (
::
) providestry.
/catch.
- Along with Adverse, the foreigns
dbsig
,dberr
, anddberm
collectively provide the functionality ofthrow.
/catcht.
- the z-locale verb
assert
provides the functionality of theassert.
control word.
It should be noted that in J, control structures are rarely used except at the outermost level of control flow, e.g. when interacting with I/O. The various primitives do a better job of implementing most looping patterns in functional contexts, because they are more composable, more directly communicative of intention, and highly optimized. The Loopless page contains a detailed classification of J’s commonly used primitives with respect to the looping patterns they correspond to.
For example, instead of using continue.
, a J programmer would usually just filter out (#~ test
) the elements that the continue.
would've skipped. Instead of using break.
, they would apply the breakout predicate to the entire array, take up to the index of the first element that passed the test ({.~ pred i.1:
), and apply the loop body verb on the result.
Note that the functional variants listed here do not attempt to emulate the "only test the first atom of the T-block" behavior that J's if. uses. For example:
if. 'chalk' = 'cheez' do. sneeze'' end.
will test true because the first atom of the evaluated T-block is 1:
'chalk' = 'cheez'
1 1 0 0 0
Instead, as is done in most other languages, we will assume the test verbs produce either a 1 (true) or a 0 (false) from processing the given array(s) in their entirety.
Flexibility of verbs
Wherever "apply verb f on [X and] Y" is mentioned (in this essay, and in NuVoc generally), realize that, effectively, f can always instead be:
- any noun N (by using N"_ or equivalently [@N)
- a verb applied on different input(s) besides [X and] Y, e.g., f@N or f&N@[ or M&f@N (equivalently, the fork M f N"_)
- a monadic verb applied on either X or Y individually (f@[ or f@])
- an effectful verb which may or may not process its argument(s), e.g., echo@'hello'
- The constant verb N"_ ignores [X and] Y and gives N, and f@N is defined to be f@(N"_), i.e., it ignores [X and] Y, and gives (f N).
For example:
99 |@_3 ]100
3
99 (2&*@_3) 100
_6
1 |.&0 1 2 3@[ 'abc'
1 2 3 0
1 echo@'hello' ]'abc'
hello
While loop (while.
) and variations
Non-converging While loop
The familiar "While" idiom {{u^:v^:_}}
works as a subsitute for while.
except in cases where one or both operands are nondeterministic (i.e. not mathematical functions). In these cases it stops early when u
produces the same result twice in a row. If the program receives an invalid input from the user, for example, resulting in the state remaining unchanged from one iteration to the next, the loop exits. To avoid this we can use:
Wh=: {{[ F.(u [_2:Z:-.@:v)}}
The following example illustrates the difference from {{u^:v^:_}}
:
kbd=: 1!:1@1 .. read keyboard input
'result: ' , {.@kbd Wh ('q'-.@-:]) 0
a
a
a
q
result: q
'result: ' , {.@kbd ^: ('q'-.@-:]) ^:_ ]0
a
a
result: a
When called dyadically, x u Wh v y
is equivalent to (x&u)Wh(x&v) y, meaning x is used as the control argument for both u and v. More commonly, though, you want x to go into the loop body but not the predicate, or vice versa. To do this, couple the control arg(s) with u and/or v.
(2&*) Wh (100&>) 3 .. multiply by 2 while less than 100 (monadic usage)
128
2 * Wh (100>]) 3 .. equivalent dyadic usage
128
100 (2*]) Wh > 3 .. ditto
128
Next, a variant that collects the intermediate results:
CWh=: {{] F: (u [_2:Z:-.@:v)}}
?@5 CWh (3~:]) 0 .. generate random integers in interval [0,4] until we get 3
0 0 2 2 4 0 4 1 3
Converging While loop idiom
CvgWh =: {{u^:v^:_}}
CvgCollWh=: {{u^:v^:a:}} .. collect intermediate results
This construct has the same functionality as Wh
above, except that it stops and returns the result as soon as is receives the same input for two consecutive iterations; thus it behaves differently only when one or both operands are stateful computations. CvgWh
can safely be used for anything where the result is guaranteed to change in each iteration.
The construct works as follows:
f^:_
(Converge) runs f on [x and] y until the result converges—in other words, until one iteration gives the same result (within the given comparison tolerance—see also Fit (Customize) as the previous iteration.- as soon as the predicate fails (i.e., is false), it gives 0 as the right operand of Dynamic Power, meaning the loop body gets applied 0 times.
- now, since this iteration of the loop effectively did a no-op, the result is the same as that of the previous iteration.
- thus (^:_) sees that the loop has converged (i.e., successive iterations would all be no-ops since the predicate would fail each time from here forward), so the loop ends and the result is returned.
DoWhile loop (whilst.
)
Run the loop body once unconditionally before applying the test the first time.
DoWhile=: {{u Wh v @:u}}
For loop (for.
and for_ijk.
)
for.
Power of Verb (^:
) is the idiomatic way to successively apply a verb N times on [X and] Y:
{{for. i.5 do. y=.1+y end.}} 0
5
for=: {{u^:(#n)}}
{{1+y}} for (i.5) 0
5
1&+^:5]0
5
for_ijk.
J’s {{for_j. J do. y=.f y end. y}}
loop construct iterates the do.
block #J
times, and assigns the terms j
and j_index
to the Nth item of J, and to N, respectively, within the Nth iteration of the loop. If you need to reference the Nth item of J, or its respective index, in each iteration, it's cleaner to just use the for_ijk.
control word. Otherwise we need much packing and unpacking. But, for the sake of demonstration, we'll look at how we could write the equivalent functionality from scratch. The following example for_ijk. loop:
{{z=.i.1 0 [c=. 1{.~- d=.1+y-x
for_j. (d-1+y)+/&i.d do. z=. (c#j),.z{~;(-c){.&.><i.{.c=. +/\.c end.
}}
is equivalent to
{{z=.i.1 0 [c=.1{.~- d=.1+y-x
0 {:: {{j=.{.J ['z c J'=.y
((c#j),.z{~;(-c){.&.><i.{.c); (c=.+/\.c); }.J
}}^:(#J) z;c;J=.(d-1+y)+/&i.d
}}
The general template is
{{j=.{.J ['var1 var2 … j_index J'=.y
var1_expr; var2expr; …; (>:j_index); }.J}}^:(#J) var1;var2;…0;J=.J_expr
where one or more var_exprs involve j
and/or j_index
.
Switch (select. case. fcase.)
J’s select.
/case.
/fcase.
is easily emulated using Agenda. The template is
f0`f1`f2`defaultVerb@.(cases&i.)
For example, the following
'1'&,`(3&#)`(>:&.(a.&i.))`('default'"_)@.('abc'&i.) 'b'
bbb
is equivalent to
{{select. y
case. 'a' do. 1,y end.
case. 'b' do. 3#y end.
case. 'c' do. >:&.(a.&i.)y end.
case. do. 'default' end.
end.
}} 'b'
bbb
Furthermore, Antibase (#:
) can be used to "compress" boilerplate case.
statements for symmetric data.
Consider this code from the Rosetta Code page for the Robots game:
select. y
case.'y'do.move _1 _1
case.'k'do.move 0 _1
case.'u'do.move 1 _1
case.'h'do.move _1 0
case.' 'do.move 0 0
case.'l'do.move 1 0
case.'b'do.move _1 1
case.'j'do.move 0 1
case.'n'do.move 1 1
case.'w'do.giveup''
case.'t'do.teleport''
end.
Using Antibase and Agenda, it can be translated to:
giveup`teleport`([: move _1 0 1{~3 3#:'yhbk juln'i.])@.('wt'i.])
or using Cond, defined in a later section:
('w'&=)` giveup` ...
('t'&=)` teleport` ...
([: move _1 0 1{~3 3#:'yhbk juln'i.]) Cond
How did we translate from the select.
code to the equivalent Agenda code? First we sort the array of possible character inputs by the table formed by the corresponding numeric lists, then use Antibase to create the appropriate mapping:
M=: >_1 _1;0 _1;1 _1;_1 0;0 0;1 0;_1 1;0 1;1 1
C=: 'ykuh lbjnwt'
]C1=: C/:M NB. new character vector to pass as y-argument to dyadic #:
yhbk juln
NB. show that new order matches Antibase and preserves mapping:
(,.C1) ; /:~&.> M;M{0 1 2
┌─┬─────┬───┐
│y│_1 _1│0 0│
│h│_1 0│0 1│
│b│_1 1│0 2│
│k│ 0 _1│1 0│
│ │ 0 0│1 1│
│j│ 0 1│1 2│
│u│ 1 _1│2 0│
│l│ 1 0│2 1│
│n│ 1 1│2 2│
└─┴─────┴───┘
We can see that the mapping is preserved, and that the characters are ordered in accordance with Antibase (rightmost box). Note that the character order is different (it does visually look similar to the old one). Now we can translate this using Antibase with the new character sequence C1
. M
contains 3 unique values and 2 columns, so 3 3 is the radix with which to encode: 3 3#:C1
. This maps C1
to [the matrix of 0’s, 1’s, and 2’s] shown in the rightmost box, above. Finally, when we process a given character input, producing the corresponding pair of 0’s/1’s/2’s, we translate that back into _1’s/0’s/1’s by indexing into _1 0 1.
Interval Switch
Apply the appropriate verb based on what interval (tier) the input fits into, e.g., different tax brackets.
NB. f`g`h@.(intervals&I.)
%:`-:`+:`%@.(10 50 90&I.)"0] 51 2 100 20
102 1.41421 0.01 10
Cond (series of if./elseif. statements)
Cond is a symmetric treatment of if/else clauses, used in various functional languages including Lisp, k, and Factor. Return the result [x] f y
if test [x] t0 y
succeeds, else return [x] g y
if test [x] t1 y
succeeds, etc., or return the default result [x] defVb y
if none of the tests succeed:
f`g`h`defVb@.(1 i.~t0,t1,t2)
This works for most cases, where all the tests are "pure" verbs, i.e., none of them produces side-effects, e.g. sending a query to a database. It's fine if the "then" verbs are effectful, but not if the tests are, because this version runs every test. In the general case, where tests themselves might be effectful, we need a short-circuiting version in which each test is run only if all prior tests have failed:
Cond=: {{ NB. adverb
ap=. {{y`:6>x}}`{{y`:6&>/x}}@.(2=#@[)
< :(,&<) ($: 2&}.)`(ap 1&{)@.(ap {.)`ap@.(1=#@]){{x u y}}f. m"_
}}
Note''
if 1=#M -> [x] M`:6 y
if [x] M@.0 y -> [x] M@.1 y
else -> ([x coupled with] y) $: 2}.M
)
Notes:
- [x] m`:6 y applies gerund m to [x and] y.
- ap and < :(,&<) allow for ambivalent use of [x] m Cond y
- We must limit the scope of $: since we want < :(,&<) to apply once only: to the original [x and] y argument(s).
- f. is used because otherwise the derived verb m Cond would include the private name ap, and would thus give a value error for ap when executed.
If the gerund list is of odd length, the last verb is treated as the "else" branch.
Let's compare Cond to an equivalent if/else block. The following if/else statement:
{{if. (x test0 y) do. N elseif. (y test1 M) do. (x f y) elseif. (test2 x) do. (3 g 1) else. defVal end.}}
is equivalent to
test0`(N"_)`(test1&M@])`f`(test2@[)`(3 g 1:)`(defVal"_) Cond
Or, expressed on multiple lines:
{{if. (x test0 y) do. N
elseif. (y test1 M) do. (x f y)
elseif. (test2 x) do. (3 g 1)
else. defVal
end.
}}
is equivalent to:
test0` (N"_)` ...
(test1&M@])` f` ...
(test2@[)` (3 g 1:)` ...
(defVal"_) Cond
Example:
G=. (0=5&|)`+: `(>&100)`%: `(=&7)`('seven'"_)
G`('else'"_) Cond&.> 113 500 8 7 14
┌───────┬────┬────┬─────┬────┐
│10.6301│1000│else│seven│else│
└───────┴────┴────┴─────┴────┘
G`- Cond&> 113 500 8 14
10.6301 1000 _8 _14
NB. dyadic use:
3 (2=|@:-)` (!@])` ... NB. if 2=|x-y -> !y
(0>])` ([^2:)` ... NB. else if y is neg. -> x^2
(0=7|])` (%:@:*)` ... NB. else if 7 divides y -> %:x*y
(-@]) Cond"0] _4 49 11 5 NB. else -y
9 12.1244 _11 120
Translating a block of if-else's from the Death Star problem on Rosetta Code:
Original:
getvec =: 4 :0 "1
pt =. y
'pos posr neg negr'=. x
if. (dot~ pt-}:pos) > *:posr do. 0 0 0
else.
zb =. ({:pos) (-,+) posr -&.:*: pt mag@:- }:pos
if. (dot~ pt-}:neg) > *:negr do. (pt,{:zb) - pos
else.
zs =. ({:neg) (-,+) negr -&.:*: pt mag@:- }:neg
if. zs >&{. zb do. (pt,{:zb) - pos
elseif. zs >&{: zb do. 0 0 0
elseif. ({.zs) < ({:zb) do. neg - pt,{.zs
elseif. do. (pt,{.zb) - pos end.
end.
end.
)
With Cond:
getvec =: ...
(*:@posr < [:dot~ pt-}:@pos)` (0 0 0"_)` ...
(*:@negr < [:dot~ pt-}:@neg)` ((pt,{:@zb)-pos)` ...
(zs >&{. zb)` ((pt,{:@zb)-pos)` ...
(zs >&{: zb)` (0 0 0"_)` ...
({.@zs < {:@zb)` (neg - pt,{.@zs)` ...
(pt,{.@zb) Cond"1 ...
...
[.(zs=. {:@neg (-,+) negr -&.:*: pt mag@:- }:@neg) ...
[.(zb=. {:@pos (-,+) posr -&.:*: pt mag@:- }:@pos) ...
[.('`pt pos posr neg negr'=. ]`(,{{(y{[)`''}}&>i.4))
This translation is adequate in most cases. But in the most general case we must tweak it a bit. Suppose that one or more intermediate calculations, upon which subsequent expressions depend (e.g. the nouns zb and zs from the original if/else code block), had been the results of stateful computations. Let’s say we’re placing a stock trade through an API and getting the response message about whether the order completed successfully, and that subsequent tests and/or "then" verbs depend on that result. In that case, we can't evaluate such expressions early as we did in the above example. To execute such expressions only when all preceding tests have failed, we can make the following changes (supposing that zb and zs are the results of stateful computations):
getvec =: ...
(*:@posr < [:dot~ pt-}:@pos)` (0 0 0"_)` ...
(0:@:".@set_zb)` ] ... NB. (new)
(*:@negr < [:dot~ pt-}:@neg)` ((pt,{:@zb)-pos)` ...
(0:@:".@set_zs)` ] ... NB. (new)
(zs"_ >&{. zb"_)` ((pt,{:@zb)-pos)` ...
(zs"_ >&{: zb"_)` (0 0 0"_)` ...
({.@zs < {:@zb)` (neg - pt,{.@zs)` ...
(pt,{.@zb) Cond"1 ...
...
[.(set_zs=. 'zs=. x ({:@neg (-,+) negr -&.:*: pt mag@:- }:@neg) y') ... NB. (changed)
[.(set_zb=. 'zb=. x ({:@pos (-,+) posr -&.:*: pt mag@:- }:@pos) y') ... NB. (changed)
[.('`pt pos posr neg negr'=. ]`(,{{(y{[)`''}}&>i.4))
Here we replace the verb zb with a string that produces the corresponding noun result and assigns to it, and we conditionally execute the assignment zb=.noun_phrase within Cond. We then give a 0 so that Cond will continue evaluating the next predicate. Finally, we make each noun into a constant verb so that the resulting expression will still be a verb, in order to pass it as an operand to Tie (`). Since the former verb definition of zb (the one used within set_zb) was written tacitly and is a function of x, within the string it is applied dyadically (when the string is executed by (".) on x and y). Now every reference to zb within [the tests run by Cond] refers to the stateful result of running it the one time only.
Error handling
Adverse (try./catch.)
Adverse (u ::v
) provides the functionality of try.
/catch.
: the verb v
or noun n
is applied/given if u
fails with an error.
Additionally, the 13!:n
foreigns, for (n e. 8 11 12)
, are [raise error] (equivalent to throw.
), [get last error number], and [get last error message], and each has an alias for convenience.
(dberm dbsig dberr) f. NB. show definitions
(13!:12) (13!:8) 13!:11
The idea behind throw.
, catch.
, and catcht.
is that throw.
allows us to communicate information to a higher-level catcht.
block via a global variable. So you might encounter a problem scenario within a try.
block, set the value of a given global variable based on which bad scenario was encountered, then execute throw.
, then catch the raised error within a containing catcht.
block, and branch/switch based on the global variable’s value. catch.
catches all errors except for error code 35 (the error raised by throw.
), effectively enabling the programmer to communicate "bad scenario" information to a subprogram that is higher up in the call relative to various catch.
blocks.
This dynamic can be emulated by using the unassigned error codes (currently those in the range [60, 255]). One can either branch based on the value of a global variable, or based on which unassigned error code was raised.
The idea here is to use dbsig err
, where err
is an error code in the range [60, 255], as a substitute for throw.
.
throw=: 'uncaught throw' dbsig 60"_
Catch=: {{u :: (v"_`(dberm@'' dbsig dberr@'')@.(60 <: dberr''))}}
u Catch v .. or (u Catch n)
(throw [ ".@'global_var=.''a''')`{@.*@[
Catch
is like Adverse except it catches only system errors, similar to how the catch.
control word catches everything except throw.
. Unassigned error codes are passed to the next higher-level instance of Adverse, which can be used as a substitute to catcht.
:
f :: (verbA`verbB`defaultVerb@.('ab' i. global_var))
corresponds to
{{try. f y
catcht. select. global_var
case. 'a' do. verbA y end.
case. 'b' do. verbB y end.
case. do. defaultVerb y end.
end.
}}
It isn’t ergonomic to package this expression into a modifier since it’s dependent on four different inputs besides [x and] y: the global variable, the set of its anticipated possible values, the verb to try initially, and the gerund of verbs from which to choose based on the variable’s value.
Alternatively, we can switch based on the particular unassigned error code that was raised, rather than on the value of a global variable:
CatchT=: {{m@.(n i. dberr'')}}
f :: (verbA`verbB`defaultVerb CatchT 60 61)
Assertion
assert
is a verb in the z-locale:
assert
0 0 $ 13!:8^:((0 e. ])`(12"_))
assert 1=2
|assertion failure: assert
| assert 1=2
assert>:i.4
assert i.4 .. fail; contains a 0
|assertion failure: assert
| assert i.4
Loop forever
Run g forever. We can do this by using Unlimited Fold and never calling Terminate Fold:
[X] [F.g Y
[X] g Wh 1: Y .. or this
Computations with side effects
Often we want to run a verb for its side effects and then separately continue the computation without reference to the actual result of the effectful verb. The template is [X] (f [ sfxVerb) Y
. Here sfxVerb
is applied to [x and] y, but its result is ignored in rest of the computation.
4 (+ [ echo@'hi') 6
hi
10
4 {{x+z [ echo 'intermediate value: ',":z=.2+y}} 6
intermediate value: 8
12
Verbs can also be used to modify the values of existing terms. E.g., to reassign noun terms to their equivalent constant verbs for easier use in tacit code:
'fruit six'=: 'apple';6
fruit
apple
six
6
const=: {{0 0$((":y)=:y~"_)y}}
const&> ;:'fruit six'
fruit
'apple'"_
fruit 0
apple
six 0
6
(fruit , ] ":@, six) 4 5
apple4 5 6
Now there's no need to write six"_
as the right tine of fork in order for it to be interpreted as a verb phrase. This can add convenience when several named nouns are used in various verb phrases within tacit code.