Using Insert as a J-in-5-Minutes subject
J is an array programming language in the tradition of APL and K. A unique feature of J and its family is the ability of programs to largely be written without the use of loops, though looping in J is easy to do, also. To employ the implicit looping requires a different mindset that often involves inserting clever code between consecutive data values. That skill enables writing very efficient, compact code, that can take advantage of other extremely powerful data manipulation tools provided in J.
This essay is about using
1. ) J's Insert adverb, the slash (/), for insertion, 1. ) J's verb trains, called Forks, for writing code, 1. ) J's Suffix and Prefix adverbs (\. and \), for data reconsolidation, and 1. ) J's special code for these combinations of Inserts and Suffix or Prefix.
We also become aware of J's left to right parsing without traditional arithmetic operator precedence.
To demonstrate these ideas we'll look at an example from finance that was recently discussed in the J forums.
NB. To figure out the loan balance of say a $10k loan at 5% interest NB. with payment schedule p, NB. where payments are made at end of period NB. (so interest is calculated on balance right before payment)
We start by defining a noun p which contains the $1000 payments.
p=. 10 # _1000 NB. first payment is in last position. p _1000 _1000 _1000 _1000 _1000 _1000 _1000 _1000 _1000 _1000
The solution to the loan balance question is solved with the following single line of code, so let's try to understand that code. The Insert character, slash, separates the code on its left from the data on its right. The J parser works from right to left first appending the payments in p with the loan $10,000. When the parser then sees the slash it knows that the material immediately to its left -- a verb, actually -- will be inserted in between each data pair. Because of the right to left parsing, the rightmost pair of data will be applied to the verb first. That is, the verb is applied to that pair only, until that calculation is completed. Once completed, the result will become the righthand side argument of the next same verb which is conceptually already in its place, and that next inserted copy of the verb will be applied. This verb copy already has a datum to its left for its left argument (that is the rightmost unused datum). The process is repeated for as many data values as exist. That's a lot to understand, but we will do so, slowly.
First the insert adverb must determine what is the verb to its left. The parentheses delimit the whole verb because it is a composite train of verbs. If it had been a single verb, no parentheses would be needed.
The train is quite simple to understand in this case. Again parsing from right to left, the train first mulitplies the righthand argument by the interest rate factor, 1.05. That result is then added to the lefthand argument. In the first application of the verb the lefthand argument is the payment amount, _1000, and the righthand argument is the amount borrowed, 10000.
([ + 1.05 * ]) / p , 10000 3711.05
The balance due is $3711.05.
To reduce the cognitive and screen printing load, we will temporarily redefine the payment list to just 3 items.
p=. 3 # _1000
To better see the intermediate, symbolic calculations, a new verb, z, is defined. Notice that z contains an insert verb itself, as well as containing the other pieces of the main verb, but adding and multiplying are literals, not real operations in z. I have added an extra delimiter term ']' for separating the intermediate results. On the right side of the insert verb are some verbs which convert the numeric data values to character format and laminate sequential pairs together. The details are not important to our purpose here. Suffice it to say that the verb z is inserted between the data pairs, just as the real verb was inserted above. After the main calculation, the verb deb removes many space characters from the display.
z=. (']',[,'+','1.05*',])/@,:&": deb z / p , 10000 ]_1000 +1.05*]_1000 +1.05*]_1000+1.05*10000
That result spreads out the pieces into 3 segments separated by the delimiter. The 3 segments correspond to the 3 applications of the inserted verb z between the four data values.
In the rightmost segment we see that first 10000 grows to 10500 and then the negative 1000 is added. We know that produces 9500, but that is not shown. In the other segments the same steps are repeated.
Actually the delimiter ']' was chosen for its ability to NOT influence the calculations of the symbolic result. If I just enter the output onto a fresh line of the J interpretter, the correct calculation will be performed. But we cannot verify the full answer yet because we are not using the full dataset. The answer for after these three payments is $8423.75.
]_1000 +1.05*]_1000 +1.05*]_1000+1.05*10000 8423.75
Now we look at the adverb suffix (\.), which applies its verb to each suffix of the data starting with the whole data set.
<\. p , 10000 ┌───────────────────────┬─────────────────┬───────────┬─────┐ │_1000 _1000 _1000 10000│_1000 _1000 10000│_1000 10000│10000│ └───────────────────────┴─────────────────┴───────────┴─────┘
In that result we see the first whole data set, then we drop one payment in the next group, and so on dropping one payment each time.
If our verb is inserted into each of the suffixes, we will calculate the desired balance for shorter and shorter periods as suggested by the next 2 calculations, which show the result symbolically as well as final calculations.
deb@z/each<\. p , 10000 ┌───────────────────────────────────────────┬──────────────────────────────┬─────────────────┬─────┐ │]_1000 +1.05*]_1000 +1.05*]_1000+1.05*10000│]_1000 +1.05*]_1000+1.05*10000│]_1000+1.05*10000│10000│ └───────────────────────────────────────────┴──────────────────────────────┴─────────────────┴─────┘ ". :: ] each deb@z/each<\. p , 10000 ┌───────┬────┬────┬─────┐ │8423.75│8975│9500│10000│ └───────┴────┴────┴─────┘
Now we trim the verb to its most efficient level and include all of the 10 payments. Both insert and suffix are used to compute not just the single answer originally posed, but all of the intermediate answer, too. Although it would seem that the repetitiveness of the intermediate answers would be inefficient, special code in J recognizes the pattern vv/\.y and reduces the repetition, (I hope).
vv=. [+1.05*] p=. 10 # _1000 vv/\.p,10000 3711.05 4486.72 5225.45 5929 6599.04 7237.18 7844.94 8423.75 8975 9500 10000
Try the following steps to see labels and plots.
(,:~|.@i.@#)vv/\.p,10000 10 9 8 7 6 5 4 3 2 1 0 3711.05 4486.72 5225.45 5929 6599.04 7237.18 7844.94 8423.75 8975 9500 10000 load'plot' plot;/(,:~|.@i.@#)vv/\.p,10000