Conversation started Mar 28, 2018 at 17:30.
Mar 28, 2018 17:30
Welcome to APL Cultivation!
woot \o/
Let's do this thing
It has been requested that we treat trains and tacit programming more in depth? Objections? Other ideas!?
Nope, I'm all for it.
OK, then. My plan is to go through some theory, and then show some examples. Speak up if anything is unclear.
Since I believe I was the one who requested it in the first place >.>
Mar 28, 2018 17:32
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.
That's what I would consider the hard part
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.
So 2+×÷4 is (2+4)×(2÷4)
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.
that looks like an explicit as ^^^ says
Mar 28, 2018 17:44
I start to get confused on trains of trains
@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.
@Adám 2(+×÷)4 right?
@J.Sallé Yes.
Okay good
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).
Mar 28, 2018 17:53
Fair enough
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.
tfw you have more trouble understanding the "for dummies" explanation instead of the "for more advanced people" one
@EriktheOutgolfer Uh, oh. Sounds like I'm not explaining this well.
We can even ask the chat bot to show us an image of the fork:
⎕←+×÷
@Adám
┌─┼─┐
+ × ÷
(The image becomes clearer with more complex trains.)
OK, now let's look at 4-trains. (1-trains are simply single functions.)
Consider f g h j.
We begin from the right and grab up to three functions, i.e. g h j. Those are evaluated as before. Let's call the result H.
Now we have f H. Really, f would have taken a left argument, but there isn't any, so it is just applied to H monadically.
In total, f g h j is f (g h j) or to be explicit, A (f g h j) B is f ((A g B) h (A j B)).
@all clear?
Mar 28, 2018 18:05
so using H instead of g h j, A (f H) B is f (A H B)?
@Cowsquack Yes.
TIL that substitution trick is handy.
@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).
Also, following Cows quack's logic, A(f g h j)B would be f ((A g B) h (A j B))?
@J.Sallé Um, yes, that's what I wrote ^^^^^^^
Mar 28, 2018 18:08
oh duh
I didn't notice that
My bad, carry on please >.>
@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.
@Adám TIL number 2.
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÷⍨⊢.
Or ÷∘42
@H.PWiz Yes, but that isn't a train, and you can't use it dyadically.
Mar 28, 2018 18:17
Question: How is the commute operator processed in trains? Same way as any other?
E.g.: A(×⍨+)B
@J.Sallé The operators bind functions before anything is evaluated, so ÷⍨ just becomes the middle function.
@Adám It's not a 1-train?
1-train is nonsensical
@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).
@Adám which is itself a 3-train?
Mar 28, 2018 18:21
@Adám I do agree, although I don't distinguish between trains and functions too much
h j k evaluates as a normal 3-train, and its result (let's call it J) becomes the right argument of g, so f g J.
And then the pattern just repeats.
@J.Sallé Yes. Really there are no other trains than 2-trains (atops) and 3-trains (forks).
A 4-train is an atop of a fork, and a 5-train is a fork of a fork, and a 6-train is an atop of a fork of a fork, etc. etc.
@Adám TIL number 3 I guess >.>
This is really all there is to Dyalog APL trains, but let's look at some handy identities, and then we'll convert some dfns to trains.
Because (f g) B is f (g B) then if g is , then (f g) is just f.
Because A (f g h) B is (A f B) g (A h B) then if f is and h is , then (f g h) is just g.
Because A (f g h) B is (A f B) g (A h B) then if f is and h is , then (f g h) is just g⍨.
We could of course make many more such identities, but I'm sure you get the idea, so just one more:
Because (f g) B is f g B and f∘g B is also f g B, we can substitute (f g) with f∘g in monadic cases.
OK, let's look at Zacharý's dfn {(,⍨⍴⍵↑⍨×⍨)⌈.5*⍨≢⍵}.
Note that converting to tacit form doesn't always make the code shorter. This is just for the exercise.
Oh this is gonna be fun
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:
Mar 28, 2018 18:37
@Adám lol
⊢ (,⍨⍴(⊢⊣)↑⍨×⍨) (⌈(.5*⍨(≢⊢)))
why not simply have (⊣) instead of (⊢⊣)? (or why doesn't that work?)
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:
@Cowsquack We will. Not done yet.
Step-by-step solutions ftw
⊢ ((,⍨⊢)⍴⊣↑⍨(×⍨⊢)) (⌈(.5*⍨(≢⊢)))
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!
Mar 28, 2018 18:45
⎕←⊢((⊢,⊢)⍴⊣↑⍨⊢×⊢)∘⌈.5*⍨≢
@H.PWiz
       ┌─┼────────┐
       ⊢ ∘    ┌───┼─┐
      ┌──┴──┐ 0.5 ⍨ ≢
  ┌───┼───┐ ⌈   ┌─┘
┌─┼─┐ ⍴ ┌─┼───┐ *
⊢ , ⊢   ⊣ ⍨ ┌─┼─┐
        ┌─┘ ⊢ × ⊢
        ↑
@Adám uh, can't you do 2*⍨⊢ instead of ⊢×⊢?
@Adám indeed it does.
@EriktheOutgolfer Yes, but that's longer.
@Adám I meant in the sense of clarity
Mar 28, 2018 18:46
We're golfing, we don't need clarity >.>
@EriktheOutgolfer Oh yes, but I wouldn't code like this anyway. It was just an educational example.
Let's do one more: Moris Zucca's dfn {⊃⍵[(⍳⍴⍵)~⍵⍳⍵]}.
OK, right away we can spot an issue here. You can't use bracket indexing in a train, but luckily there is a functional alternative in the primitive.
So, first let's substitute that in:
@Adám yeah trains are for short functions
{⊃⍵⌷⍨⊂(⍳⍴⍵)~⍵⍳⍵}
@EriktheOutgolfer That's a good rule of thumb. I personally try to avoid parentheses. If my train needs one, I'll split it up instead.
No let's do our substitution:
⊃⊢⌷⍨⊂(⍳⍴⊢)~⊢⍳⊢
Just a couple of things to fix in this one: ⍳⍴⊢ won't work, and is called monadically, but we can easily fix those:
⊃⊢⌷⍨(⊂((⍳⍴)⊢)~⊢⍳⊢)
There. But now we've got a f ⊢ case in (⍳⍴)⊢, so we'll simplify as per the identity above:
⊃⊢⌷⍨(⊂(⍳⍴)~⊢⍳⊢)
And since (⍳⍴) is called monadically, we can use ⍳∘⍴:
⊃⊢⌷⍨(⊂⍳∘⍴~⊢⍳⊢)
Note that the rightmost uses the same left and right argument, so it is a selfie: ⍳⍨
⊃⊢⌷⍨(⊂⍳∘⍴~⍳⍨)
Finally, is called monadically, so we can glue it to ⌷⍨:
⊃⊢⌷⍨∘⊂⍳∘⍴~⍳⍨
Maybe we should tell Moris he can save a few bytes?
ngn
ngn
can we take advantage of the fact that only the first element is returned?
@ngn Possibly, but that isn't the subject of this lesson.
Mar 28, 2018 18:56
@Adám hm, later ^
OK, I'll do one last one, but we'll probably end up going over time.
but he doesn't seem to specify "Dyalog" specifically
My dfn {⍵⊆⍨(⍴⍵)↑⍺/+\⍺}
@EriktheOutgolfer He uses Dyalog APL (works for one of our biggest customers).
This one is fun. Let's start with substitution:
@Adám that doesn't mean he wrote the answer in it though, so maybe you want to specify it explicitly
⊢⊆⍨(⍴⊢)↑⊣/+\⊣
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):
⊢⊆⍨(⍴⊢)↑⊣(/⍨⍨)(+\⊣)
Mar 28, 2018 19:02
@Adám ``+\``
But since we're anyway swapping arguments (twice) we may as well just swap the actual and (+\⊣) instead.
@EriktheOutgolfer Yeah, too late. Should be +\.
@Adám you got it the first time :P
@EriktheOutgolfer Oh, I see, the rendering was just slow and I only saw the first step.
Anyway, swapping arguments:
⊢⊆⍨(⍴⊢)↑(+\⊣)(/⍨)⊣
And we're done. For one more example of this, see the transcript‌​.
Thank you all for participating! I hope it was useful.
 
Conversation ended Mar 28, 2018 at 19:06.