Conversation started Jul 28, 2020 at 14:30.
Jul 28, 2020 14:30
Welcome to APL Cultivation!
We've are going through the new stuff in 18.0.
Last time, we covered the unique mask function and the constant operator.
This time, I'd like to go over the new compositional operators, Atop and Over.
First Atop, which has been assigned function⍤function thus sharing the symbol with the rank operator's function⍤array
You should be familiar with the 2-train, which is also called "atop": (f g)Y and X(f g)Y.
Maybe you've even been burned by f∘g Y being an atop, but X f∘g Y not being an atop.
Well, the atop operator is what you would expect, i.e. f⍤g Y is exactly like f∘g Y but X f⍤g Y is f X g Y or X (f g) Y.
I strongly recommend transitioning to use in places where you've hitherto used monadic f∘g.
RGS
RGS
@Adám why?
Because it will prevent (at least one potential cause of) frustration if you ever decide to add a left argument to your code.
RGS
RGS
I was taught to look at (⍺) f∘g ⍵ as (⍺) f with the right argument preprocessed by g.
Right, but it is easy to forget that if you use f monadically.
RGS
RGS
@Adám Yes, I am just trying to understand how/why you can be so sure that the most useful dyadic extension to that interpretation is with ⍤ and not ∘
Jul 28, 2020 14:41
Let's say you define a function that returns the magnitude of reciprocal.
      |∘÷ ¯4
0.25
      |∘÷ ¯5
0.2
(I realise that this could be written without the but I've opted for a very simple function for illustration purposes.)
RGS
RGS
@Adám (ofc)
@RGS i recently have started to strongly prefer to not reference the only argument in monadic application as "the right argument"
RGS
RGS
@dzaima what do you call it, then? only "argument"?
@RGS yep
Now you get a feature request that the function should take a left argument which is a numerator (instead of the default 1).
      2 |∘÷ ¯4
1.75
      2 |∘÷ ¯5
1.8
RGS
RGS
Jul 28, 2020 14:43
@Adám :)
Oops. However:
      |⍤÷ ¯4
0.25
      |⍤÷ ¯5
0.2
      2 |⍤÷ ¯4
0.5
      2 |⍤÷ ¯5
0.4
@RGS Well, you can't of course. So by all means, continue to use if indeed you mean preprocess the (right) argument. But if, conceptually, you are simply applying one function after another, then use
RGS
RGS
Of course you engineered an example that was good for you and the example made sense; thanks. I think ⍤ is particularly helpful when dyadic g is similar to monadic g, like - and ÷. My take on this is that when dyadic g is not that related to monadic g, using ∘ might be as suitable as ⍤ a priori, but then dyadic g ≠ monadic g means I probably won't be in any situations similar to the one you just described.
So I can just roll with f⍤g for the monadic cases; when extensions make sense ⍤ is already there, when they make no sense it makes no difference
Sometimes you might want to add a left argument to the left function. In that case, is the right choice.
RGS
RGS
@Adám Yup, just corrected "roll with f⍤g for the monadic cases"; in the dyadic case f∘g still preprocesses the right arg. Very nice! I am happy to move on now :)
An equivalent definition of our above function would be ÷∘| and there you don't want as the added left argument is for ÷
RGS
RGS
Jul 28, 2020 14:48
@Adám true, but now we are not returning a magnitude anymore, as ¯1 ÷∘| 1 returns ¯1
OK, I assumed a non-negative left argument.
One way to look at f∘g vs f⍤g is that when given a left argument, gives it to the left-hand function and gives it to the right-hand function.
Other than that, they are equivalent.
RGS
RGS
@Adám +← 1
Another way to look at f∘g vs f⍤g is simply choosing order of the first two tokens in the equivalent explicit expression: X f∘g Y computes X f g Y and X f⍤g Y computes f X g Y
So we're simply swapping X and f.
Then there's the classic problem with slashes, especially in tacit programming.
If you've ever tried using replicate/compress in a train, you'll have bumped into the fact that slashes prefer being operators over being functions.
This means that {(5<⍵)/⍵} doesn't convert to (5<⊢)/⊢
RGS
RGS
@Adám particularly annoying, yes
While it may not be obvious at first sight, if we define f←5<⊢ it might become clearer that f/⊢ isn't at all what we want.
Now, there's an axiom in APL that an operator cannot be an operand. (Shh, don't mention ∘.f)
This means that if a slash ends up in a situation where it has to be an operand, it will resort to being a function.
You may even have noticed that constructs like ⊢(/⍨)5<⊢ work fine, though ⊢/⍨5<⊢ doesn't.
This is because the / in isolation with the is forced to become the operand of . But since operators bind from the left, ⊢/ binds first, and so ⊢/⍨5<⊢ becomes (⊢/)⍨5<⊢ or (5<⊢)⊢/(5<⊢) which is usually not what you want. (Ping me if you find an example where you actually do want that!)
So, to the rescue.
If (or any dyadic operator) is found to the immediate left of a slash, then clearly the dyadic operator cannot be the operand of the slash, being a dyadic operator itself, and it can't be part of the function on the left, since it requires a right-operand too.
Therefore, the slash is forced to become a function.
So -⍤/ is the negation of the replicate:
RGS
RGS
Jul 28, 2020 15:03
@Adám so how would we use it? ⊢⍤/ ?
       1 0 2-⍤/10 20 30
¯10 ¯30 ¯30
Nice
@RGS Yes, if you don't actually want any function applied to the result of the replication, you'd use the identity function like that.
RGS
RGS
awesome
It is easy to think then that "oh, this is an atop, so I should be able to do this with parentheses too; (f g)" but that'd be a mistake:
(-/) is just a normal minus-reduction.
RGS
RGS
Jul 28, 2020 15:05
@ngn (without most of the comments I get a 7.04kB file; ×5 sounds good enough for me :P )
@Adám I didn't understand
Well, since f⍤g is "atop" and (f g) is "atop", you might think they are interchangable.
RGS
RGS
@Adám ah ok, I get what you mean
Another mistake is to think: "if a slash is an operand, it'll be a function" and then think that /∘⊢ would work like ⊢⍤/ by pre-processing the right argument with a no-op rather than post-processing the result with a no-op.
RGS
RGS
@Adám you mean that /∘⊢ inside a train will still bind a function to / if / is not the leftmost function?
Yes, it'd also work if you give it a name or parenthesise it, but it won't work inline in a train, because f /∘⊢ g will be bound as (f/)∘⊢ g
Btw, it isn't just inside trains you hit this
Let's say we have a two-element vector of a mask and some data, and you want to "apply" the mask to the data…
Challenge: Write a function Apply which takes an argument consisting of two vectors; a mask and some data, and computes mask/data
RGS
RGS
Jul 28, 2020 15:14
Apply ← //
      //(1 0 1)'abc'
┌──┐
│ac│
└──┘
@RGS That gives an enclosed result ^
Does work up to this point, but will break if you try to chain anything on the left
RGS
RGS
Ah so the point is that you don't want the enclosure?
yes.
RGS
RGS
Jul 28, 2020 15:15
Apply ← ⊃⍤(//)
So the solution will be ⊃(//) or ⊃⊢⍤//
Yup, any one of those three will do.
In fact, once ⊢⍤/ becomes a common pattern, you can actually help the reader of your code by using ⊢⍤/ so they don't have to consider if your slash is Replicate or Reduce.
For example, if your code says z←x/y it might not be obvious what's going on.
RGS
RGS
@Adám being that mine is a single function and Bubbler's are atops, they have different usages inside trains, no?
But if you write z←x⊢⍤/y your reader knows exactly what you're doing.
@RGS Sure. But if you name it, it makes no difference. (Also, yours is an atop too!)
RGS
RGS
@Adám Mine is an atop too but a single function; without naming it ⊃⍤(//) will behave differently in a train when compared to the other two. Or did I get it wrong?
I'm just trying to get everything right
Jul 28, 2020 15:21
Bubblers 2-trains would need parenthesising if used inside a train, yes.
@RGS Of course if I needed to embed it further in a train, I'd have written like yours
RGS
RGS
@Bubbler ok ok so it is somewhat different; thanks
OK, anything else about Atop before we move on to Over?
RGS
RGS
@Adám hm, another challenge? :P
Let's see if I can think something up…
OK, given a string, replace every character with two copies of itself prefixed and suffixed by a space.
E.g. 'abc' becomes ' aa bb cc '
Yes, you can do this with regex. Please don't.
RGS
RGS
Jul 28, 2020 15:26
@Adám oh dang
Otherwise, I'll just change the data type :-)
10 20 30 should become 0 10 10 0 0 20 20 0 0 30 30 0
{' ',⍵,⍵,' '}¨
@xpqz That needs flattening, and fails on other data types.
      YourFunction 3 4⍴⎕A

ABCD
ABCD


EFGH
EFGH


IJKL
IJKL

⍝ ↑ all-space line
RGS
RGS
{⍵\⍨¯1,¯1,⍨2ׯ1*⍳¯1+2×≢⍵}
(∊¯1 2 ¯1⍨⍤¯1)⊢⍤⍀⊢
RGS
RGS
Jul 28, 2020 15:31
So that is not very different from mine, in idea. You "just" wrote a train and made it shorter; correct?
Also featuring Constant :)
Right, and Bubbler got the hint that you want to use to force to become a function.
RGS
RGS
@Adám I couldn't come up with a solution using ⍤ ⍥
@Bubbler Try getting rid of the parenthesis.
@RGS Try ^ or make your own tacit.
Done: ⊢⊢⍤⍀⍨¯1 2 ¯1⍴⍨3×≢
RGS
RGS
Jul 28, 2020 15:34
f⍤¯1 applies on what cells?
@RGS Major cells.
RGS
RGS
@Bubbler +← 1
i.e. the cells with one rank lower than the whole array
@Bubbler That's basically my solution.
@Bubbler I bow to your superior skills
Jul 28, 2020 15:36
You can do it too, if you write 100 code golf solutions in APL.
@Bubbler A more direct translation: ⊢⊢⍤⍀⍨∘∊¯1 2 ¯1⍨⍤¯1
@Bubbler Oh well, I out-golfed you by two bytes: ⊢⊢⍤⍀⍨0 2 0⍴⍨3×≢
Damn zeros.
Yeah, they are quirky.
OK, let's see if we can cover Over as well.
So, remember how f∘g preprocesses the right argument of f using g?
One way to look at Over is simply as preprocessing all arguments of f using g.
All as in both or the only.
So again f⍥g Y is the same as f⍤g Y and f∘g Y.
The difference is again when we do a dyadic application.
So while X f∘g Y is X f(g Y) we have X f⍥g Y be (g X)f(g Y).
This may seem like an overly involved operator, but really, the pattern of preprocessing both arguments comes up a lot. Once you start looking for it, you'll see it all over ;-)
RGS
RGS
@Adám ah, I see what you did there.
I was just staring at (g X)f(g Y) and thinking 'everywhere'
Jul 28, 2020 15:45
Challenge: Dyadic function computing the sum of absolute values of its arguments.
+⍥|
(Compare that to the old-fashioned +/∘|, or even ⊃(+/∘|{⍺⍵}))
Yup. And: Given arguments which are vectors, which one has the smallest maximum? Return ¯1 if the left argument has the smallest maximum, 1 if the right one has, or 0 if they are equal.
RGS
RGS
×⍤-⍥(⌈/)
@RGS Oops, someone forgot the first part of this lesson!
RGS
RGS
woops
Jul 28, 2020 15:52
But now it is good. Beautiful use of both Atop and Over.
You can of course omit the here, unless used inline.
OK, how about this: Write an alternative to replicate which can take arguments of equal shape, both with rank greater than 1, and replicates the corresponding elements. Since the result might otherwise be ragged, you have to return a vector.
      (2 3⍴⍳6) YourFunction 2 3⍴⎕A
ABBCCCDDDDEEEEEFFFFFF
Shouldn't it be "equal shape" to make sense?
@Bubbler Yes, thanks.
RGS
RGS
@Adám ⊢⍤/⍥∊
I'd use , instead of
RGS
RGS
@Bubbler why?
Jul 28, 2020 15:57
Because we don't want to open up the elements of the right argument.
Right arg could be nested
Also, in this case, you don't need ⊢⍤ but it is good for clarity, and necessary if used inline in a train.
RGS
RGS
@Bubbler Ah, hence shape
One of my favourites lately is ,⍥⊂ which I prefer over {⍺ ⍵}
There are lots of these. Sometimes the operand can be huge too.
Anyway, enough for today. Thank you so much for participating!
Jul 28, 2020 16:01
@Bubbler Ah, right. I forgot about that.
Thanks @Adám - lots of brain gymnastics
Good.
RGS
RGS
@Adám thanks for your time ⍥/
A golfing tip regarding : you can sometimes use it to preprocess the left argument, when it is a no-op on the right.
e.g. Transpose the left arg when the right is a vector or scalar
@Bubbler Ah yes, good point:
Jul 24 at 9:46, by Adám
That's a nice one. I had 1≡⍥,≡,⍴
Here, ⍥, only ravels the left argument, since the right argument already is a vector.
Jul 28, 2020 16:08
That's a nice example too.
 
Conversation ended Jul 28, 2020 at 16:08.