10
$\begingroup$

This question is related to this golfing question (but I'm not interested in golfing, only in functional operator composition):

How can we convert f[a,b][c,d] to {a+c,b+d} using only operator forms and function compositions?

So far I've figured out two ways of converting f[a][b] to a+b:

Apply[Curry[Plus]] ~ Operate ~ f[a][b]
(*    a + b    *)

Plus @@ Apply[Curry[List]] ~ Operate ~ f[a][b]
(*    a + b    *)

but I'm stumped by the double-argument forms of the first problem, which interferes with currying. Do you know how to solve this?

$\endgroup$
12
$\begingroup$

Both Operate and Curry don't have access to the full expression, acting mostly on the head. This is why both of them take the depth as an optional argument, to know where to stop, since there is no other way for them to know - their modus operandi is limited to a single interation of evaluation sequence.

What you can do is something like this:

curry[expr_, _, 0]:=expr;

curry[head_Symbol, combiner_, n_:2][args___] := 
  curry[head[], combiner, n][args];

curry[head_[prev___], combiner_, n_][args___] := 
  curry[head[prev, combiner[args]], combiner, n-1]

which then can be used as:

Operate[Apply[curry[List, Plus]], f[a, b][c, d]]

(* {a + b, c + d} *)

But in a more complex case like e.g. f[a, b][c, d][e, f], you would have to manually set the depth for both Operate and curry:

Operate[Apply[curry[List, Plus, 3]], f[a, b][c, d][e, f], 2]

(* {a + b, c + d, e + f} *)

Not sure how much this takes it away from the operator form paradigm you fancy, but I don't how this could be done much differently, without using some hacks (such as using the Stack), which essentially would still serve to get a hold on entire expression rather than just its left-most head.

| improve this answer | |
$\endgroup$
  • $\begingroup$ A two-argument curry, wow this is really good. Makes me wonder why Curry isn't more flexible. Needing to specify the depth, even twice, is of course a hack; but your answer already teaches me a lot. Thank you Leonid! $\endgroup$ – Roman Jun 15 '19 at 17:07
  • $\begingroup$ To get the order of the summation right, we can do Plus @@ Operate[Apply[curry[List,List]], f[a,b][c,d]] to get the desired {a+c, b+d}. For larger terms, Plus @@ Operate[Apply[curry[List,List,4]], f[a,b][c,d][e,f][g,h], 3] gives {a+c+e+g, b+d+f+h}. This usage invites the definition of listCurry[n_: 2] := curry[List, List, n] as a curried-argument-to-list-of-lists converter helper: Plus @@ Operate[Apply[listCurry[]], f[a,b][c,d]] then gives the desired {a+c, b+d}. Alternatively, make default values for the first two arguments of curry be List. $\endgroup$ – Roman Jun 15 '19 at 18:58
  • $\begingroup$ Just to give some examples of usage for Leonid's two-argument curry operator: curry[F,G][a,b,c][d,e,f] gives F[G[a,b,c], G[d,e,f]]. The function F wraps the whole expression, and each set of arguments is wrapped in G. A larger example is curry[F,G, 4][a,b][c,d][e,f][g,h] giving F[G[a,b], G[c,d], G[e,f], G[g,h]]. $\endgroup$ – Roman Jun 15 '19 at 20:25
  • $\begingroup$ Leonid, I don't think it's a good idea to auto-detect the depth using Stack or similar. What if in the last example of the previous comment I wanted to curry only the first three sets of arguments? curry[F,G, 3][a,b][c,d][e,f][g,h] correctly returns F[G[a,b], G[c,d], G[e,f]][g,h] and leaves the last argument set [g,h] alone. Auto-detection of the depth wouldn't be able to handle this case. $\endgroup$ – Roman Jun 15 '19 at 20:28
  • $\begingroup$ @Roman Sure, autodetection flies in the face of operator approach, since an operator by itself knows nothing about the depth / order. You can default the combiner to List, sure. $\endgroup$ – Leonid Shifrin Jun 15 '19 at 21:10
3
$\begingroup$

Here is my approach built on RightComposition, Through and Curry.

myOp=Curry[FixedPoint,{1,2}][Through@*{Head,Apply[List]/*Sow}/*First]/*Reap/*(#[[2,1,;;-3]]&)/*Total
myOp @ f[a, b][c, d]
(* Out[]= {a + c, b + d} *)

Where does it come from?

First we define helper operators pipe and branch:

pipe = RightComposition;
branch = Through @* {##} &;

pipe is just an alias of RightComposition for faster typing.

branch will be used to distribute functions to arguments. e.g.

branch[f, g, h] @ a
(* Out[]= {f[a], g[a], h[a]} *)
branch[f, g, h] @@ {a, b}
(* Out[]= {f[a, b], g[a, b], h[a, b]} *)
branch[F, G, H] @@@ {{a, b}, {c, d, e}}
(* Out[]= {{F[a, b], G[a, b], H[a, b]}, {F[c, d, e], G[c, d, e], H[c, d, e]}} *)

Now we can define our desired operator as following:

myOp = pipe[
  branch[Head, pipe[Apply@List, Sow]] /* First // Curry[FixedPoint, {1, 2}]
  , Reap, #[[2, 1, ;; -3]] &, Total
  ]
(* Out[]= Curry[FixedPoint, {1, 2}][(Through@*{Head, Apply[List] /* Sow}) /* 
   First] /* Reap /* (#1[[2, 1, 1 ;; -3]] &) /* Total *)

Generate a lengthy example expression:

testExpr = 5 // pipe[
   Range
   , Map@branch[x, y]
   , Fold[Apply, F, #] &
   ]
(* Out[]= F[x[1], y[1]][x[2], y[2]][x[3], y[3]][x[4], y[4]][x[5], y[5]] *)

Using myOp on testExpr gives desired result:

testExpr // myOp
(* Out[]= {x[1] + x[2] + x[3] + x[4] + x[5],  y[1] + y[2] + y[3] + y[4] + y[5]} *)
| improve this answer | |
$\endgroup$
  • 1
    $\begingroup$ Very nice, thanks @Silvia! $\endgroup$ – Roman Feb 23 at 19:45
  • $\begingroup$ @Roman Thanks for the kind words. My real intention here is promoting my branch & pipe style. :D $\endgroup$ – Silvia Feb 25 at 6:48
2
$\begingroup$

One more approach, learned a bunch tackling this. Basically grab the arguments of the functions and Sow-ing them as lists, and then grabbing the Head (leaving the previous arguments behind) and repeating until having walked through all of the Head using NestWhile

Define

ff = (Sow[List @@ #]; Head[#]) &

and

funk[t_] := Plus @@ Last@Last@Reap@NestWhile[ff, t, Length[#] > 0 &];

Test

t = f[a, b][c, d][j, k][r, s];
funk@t
(*  {a + c + j + r, b + d + k + s}  *)

Again

tt = gg[a, b, c][d, e, g][q, r, s]
funk@tt

(* {a + d + q, b + e + r, c + g + s} *)
| improve this answer | |
$\endgroup$
  • $\begingroup$ Very nice. Thanks, I learned a lot here too! I've modified your code a bit: funk = Plus @@ Reap[FixedPoint[(Sow[List @@ #]; Head[#]) &, #]][[2, 1, ;; -3]] &. Not sure about the best stopping criterion yet. Cheers! $\endgroup$ – Roman Feb 24 at 18:08
  • $\begingroup$ I started with FixedPoint but struggled with capturing just the data I wanted. Nice rewrite...cleaner function! $\endgroup$ – MikeY Feb 25 at 13:49
2
$\begingroup$

Not really what you're looking for, but another cute way to do this using the fact that a pure-function captures its Head. We use an empty function call as a terminator for our threading process.

threader =
  If[Length[{##}] > 0, 
    Insert[#0, {##}, {1, 3, 2, -1}],
    MapThread[Plus, {}]
    ] &;

threader[a, b][c, d][e, f][g, h][]

{a + c + e + g, b + d + f + h}

Note too that we could generalize this like:

threadable[f_]:=
  If[Length[{##}] > 0, 
    Insert[#0, {##}, {1, 3, 2, -1}],
    MapThread[f, {}]
    ] &;

threadable[Plus][a, b][c, d][e, f][g, h][]

{a + c + e + g, b + d + f + h}
| improve this answer | |
$\endgroup$
  • $\begingroup$ Cool, that's a nifty trick! Thanks. $\endgroup$ – Roman Feb 24 at 18:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.