Tacit programming is programming without (direct) reference to the argument(s). Of course, you still need to get the data somehow, but the idea is that a function refers to the result of that function when applied to the argument(s) instead of just referring to itself.
When you actually need to refer to an argument, you still need to apply a function to it, but since you want nothing done to the data, you'll need an identity function. Dyalog APL gives you ⊣ and ⊢ which are left and right identity, respectively. This may seem trivial, but becomes very important later.
Next, we need to understand how a train (sequence) of functions is applied to the argument(s). Since APL functions can be called monadically or dyadically (niladic functions cannot directly be used in trains), there needs to be some rules.
We also need a way to specify if we want any subsequent functions to be applied to the result of the previous functions, or on the argument(s) anew,
Let's begin with 3-trains, or f g h. I find them the simplest.
In the following, I'll call the left and right arguments A and B
And let's begin with the (albeit slightly more complicated) dyadic case, as the monadic case follows very easily from the dyadic one.
Evaluating A (f g h) B from the right, we first have h which represents A h B.
Then we move on to g which will evaluate to f g (A h B). So we need to evaluate f first.
f, behaves just like h, in that it refers to A f B.
So finally g can be evaluated as (A f B) g (A h B).
Note that there is no confusion between this last non-tacit (I call it explicit) expression and a train. You can always tell the difference between explicit and tacit APL by looking at the rightmost token. If it is an array, it is explicit, otherwise it is tacit.
Conversely, this also means that you need to separate a train from any data you want to apply it to, either by naming it in a separate statement, or by parenthesising it. Getting confused regarding this is a very common mistake.
@J.Sallé No, as the function sequence is not isolated from the data. Remember: Tacit does not address the data directly. If the data is there, it is explicit. So you need 2(+×÷)4 or MyTrain←+×÷ ⋄ 2 MyTrain 4.
Going back to our f g h train, what happens in the monadic case? The dyadic was (A f B) g (A h B), and the monadic is exactly the same, but with the As removed: (f B) g (h B).
This applies universally to all trains: The parsing is identical for monadic and dyadic calls; the functions that would address the left argument are just called monadically. Btw, this also means that ⊣ refers to the right argument when the train is called monadically.
And this leads us to 2-trains. Consider f g h again — (A f B) g (A h B). What if there was no f? I.e. we just have g h.
Since g would address its left argument, but there isn't any, it is just called monadically, i.e. A (g h) B is g (A h B).
This is known as an "atop" because the g is evaluated atop (i.e. on the top of) the result of h's application.
In contrast, 3-trains are known as "fork"s because their structure resembles a fork (like a rail switch) in that the middle function "connects" to the two sides.
@J.Sallé Yeah, and in general, you just keep substituting the three rightmost functions with their result until you're done (or have only two functions left).
@J.Sallé I figured I should give you guys some time to digest the wall of text.
One little exception which fits right in: The left side of the 3-train (left tine of a fork) may be a constant (i.e. not a function that is applied to the argument(s). It is then treated as if there had been a function there which gave that result. Let me illustrate:
A (42 g h) B is just like A ({42} g h) B where {42} is an ambivalent function which returns a constant value. So it all becomes 42 g (A h B) or (A{42}B) g (A h B) if you want.
Note that you cannot have a 2-train with a constant left side, e.g. 42 f. Neither can you have a middle tine be a constant, e.g. f 42 g. (If anything, that would just be 42, no?) Nor can you have a right hand side be a constant, as that would make your code explicit, as per above.
So what if you need a constant right-tine? E.g. for the divide-by-42 function? ⊢÷42 won't work (it'll give you the identity of the reciprocal of 42).
Then you need to supply the constant as a left tine, and swap the arguments of the middle tine, using the ⍨ (Commute) operator: 42÷⍨⊢.
@H.PWiz I you want, but so is any function. A single-car train isn't really a train, though, is it? More like a tram ;-) It is just another function (a derived one in this case).
Finally, let's have a look at a 5-train, which completes the pattern.
f g h j k: Again we begin from the right and take three functions. Now we have f g (h j k).
We can begin by substituting ⊢ for every ⍵ (the right argument).
That gives us (,⍨⍴⊢↑⍨×⍨)⌈.5*⍨≢⊢ which won't work because of how trains are evaluated, so let's fully parenthesise it:
(,⍨⍴⊢↑⍨×⍨)(⌈(.5*⍨(≢⊢)))
Note that the left parenthesis is already a train, but this still doesn't work, because that train used the constant ⍵, which we've substituted with a ⊢. But ⊢ inside the train refers to the train's own right argument, and we want the original right argument.
So we need to "feed" the left train the unadulterated argument:
But now we get another issue: The functions in that train assumed the train was called monadically. That's not the case any more, so let's insert some tacks to use the correct arguments:
OK, that was the left side. Now for the right side.
(≢⊢) becomes just ≢ as per above identity, and the rightmost parenthesis isn't needed:
⊢ ((,⍨⊢)⍴⊣↑⍨(×⍨⊢)) (⌈.5*⍨≢)
Now we can see that ⌈ is applied monadically to its right argument, so we can glue to to the left train instead:
⊢ ((,⍨⊢)⍴⊣↑⍨(×⍨⊢))∘⌈ (.5*⍨≢)
And of course, we can remove that rightmost parenthesis too:
⊢ ((,⍨⊢)⍴⊣↑⍨(×⍨⊢))∘⌈ .5*⍨≢
That's it. But we can do a little better. Notice that ,⍨ and ×⍨ are "selfies". It should be obvious that f⍨ X is the same as X f X (no matter if X is a function or a constant, btw), so we can just substitute that:
⊢ ((⊢,⊢)⍴⊣↑⍨(⊢×⊢))∘⌈ .5*⍨≢
And remove final unneeded parenthesis and the whitespace:
⊢((⊢,⊢)⍴⊣↑⍨⊢×⊢)∘⌈.5*⍨≢
There you go. Totally unreadable, but it looks cool!
OK, on the right we have a monadic `+` so we'll need to parenthesise it:
⊢⊆⍨(⍴⊢)↑⊣/(+\⊣)
But now note that / is used as a function. However, it prefers to be an operator, i.e. doing ⊣ reduction instead of ⊣ replication.
To force it into function mode, we need to make it the operand of an operator (since operators cannot be operands). We can use the trick that f⍨⍨ is the same as f (in dyadic cases):