4

I have an expression consisting of a few pure functions added together like so:

f+g+h

I want to add the bodies of these functions together and make that a pure function. Usually I would do this by finding the maximum number of arguments (maxArgs) required to fill the functions f, g and h, and then create my added function like so:

newFunc = Evaluate[Through[(f+g+h)@@Slot/@Range@maxArgs]] &;

The Evaluate here is important for a couple of reasons impertinent to this question. Just know that it is necessary to evaluate the function body.

The problem with this method is that in general, I won't always know what maxArgs will be. Technically, I could find this value by using this answer, but I'm worried about the performance and robustness of this method.

I thought that I might circumvent the need to specify a number of slots by doing this:

newFunc = Evaluate[Through[(f+g+h)[##]]]&

But Mathematica's output at this point throws an error, saying that the slots of the functions f, g and h cannot be filled from ##. I understand that this is because ## appears as just one symbol to Mathematica.

So how might I evaluate Through without specifying the number of slots I will need?

Example:

Given:

f = #&;
g = Function[{a,b,c}, a^3 - b];
h = - #1^2 + #2 &;

My desired output is produced by:

myFunc = Evaluate[Through[(f+g+h)[#1,#2,#3]]&;

The important bit here is the Evaluate. I want to evaluate the function body completely before creating the function. The problem with the code above is that I had to explicitly enter the maximum number of slots required by the pure functions. In this case, three slots were required. In general, I may be using functions that take 3 arguments, or 5, or 72, etc.

In my notebook, I will not know ahead of time how many slots will be used by these functions.

Myridium
  • 1,089
  • 5
  • 15
  • Suppose that when you use expr, that f expects 3 arguments, g expects 2 and h expects 5, how would you plan to invoke expr? – John McGee Jul 11 '15 at 13:25
  • @JohnMcGee - I would expect expr to end up with five slots. f would take its fill from the first 3, g from the first 2, and h from all of them. In other words, I would want the equivalent of expr = Through[(f+g+h)[#1,#2,#3,#4,#5]]. – Myridium Jul 11 '15 at 13:30
  • You have now introduced Derivative in your examples. Is this the only other expression that yo wish to treat like a function or are you going to add another one as soon as answers are updated to handle Derivative? Also # & + Derivative[1] and Derivative[1] + # & are surely different as the first one is (# &) + Derivative[1] whereas the second is (Derivative[1] + #) &. I think your question is not well specified at present as it is not clear what extent of heads you expect to be handled. I am going to put this on hold until you can provide an exhaustive specification. – Mr.Wizard Jul 13 '15 at 01:49
  • Through[(Derivative[1] + (# &))[#1]]... think about it... – ciao Jul 13 '15 at 03:06
  • @Mr.Wizard I've tried to condense and focus the question, please let me know if there's more I can do. – Myridium Jul 13 '15 at 04:16
  • 1
    @Myridium I apologize if I appear uncooperative but that is not my intent. However there is no universal and robust way to determine the number of arguments of a function (referencing (7040) and (56665)). One then wonders what compromise you would find acceptable? Your current example uses ArcTan, a function that has both one and two parameter forms. You must explain how such problems are to be handled if this question is to be answerable. (continued) – Mr.Wizard Jul 13 '15 at 05:58
  • 1
    @Myridium Also, functions can have different evaluation rules for symbolic or numeric arguments. Therefore the class of functions that you wish to operate upon needs to be clearly specified rather than assumed and changing with each update. – Mr.Wizard Jul 13 '15 at 06:00
  • @Mr.Wizard Ah, thank you, you have brought to my attention some technical ambiguities that I didn't realize were there! I can see some of the issues now, and in this light I will narrow the scope of my question to only pure functions. – Myridium Jul 13 '15 at 06:15
  • Is my answer in its current form a solution in that case or are there other issues? If there are please make them apparent. I look forward to your (final?) update. :-) – Mr.Wizard Jul 13 '15 at 06:20

5 Answers5

4

Reading your question and comments again, and assuming that none of your pure functions contain SlotSequence, I think maybe this will work for you:

combine[expr_] := Max[
   Cases[expr, Slot[n_] :> n, {-2}],
   Cases[expr, Verbatim[Function][x_List, __] :> Length@Unevaluated@x, {1}]
 ] // Function @@ {Through[expr @@ Array[Slot, #]]} &

Test:

f = # &;
g = Function[{a}, a^2];
h = (-2 #1 + #3) &;

combine[f + g + h]
-#1 + #1^2 + #3 &

And now also:

f = Function[{a}, a];
g = Function[{a}, a^2];
h = Function[{a, b, c}, (-2 a + c)];

combine[f + g + h]
-#1 + #1^2 + #3 &

Of course as rasher/ciao points out this doesn't work with e.g. combine[f+g+h+f+g+h] but that is because f + f evaluates to 2 f and Through only works on the level one head. If something besides Through behavior is desired that will need to be specified.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Try combine[f+g+h+f+g+h]... – ciao Jul 12 '15 at 08:13
  • @ciao - Indeed, this doesn't work in general. I don't understand the usage of the levelspec parameter in Cases here. combine fails to produce the desired output for combine[Derivative[1] + #&] although swapping those arguments works... – Myridium Jul 12 '15 at 08:35
  • I'm not even sure I understand what OP is after :=} – ciao Jul 12 '15 at 08:48
  • Upon investigation, I can see that this will only work for functions written as expressions involving slots. So this suffers the same issue as belisarius and user8074's answers. – Myridium Jul 12 '15 at 10:15
  • @ciao But that is not a problem with my code, it is simply the behavior of Through as the OP requested. – Mr.Wizard Jul 12 '15 at 20:41
  • @Myridium re: "only work for functions written as expressions involving slots" sorry, you're right, I forgot to count named arguments. However even correcting that I have no idea what combine[Derivative[1] + #&] is supposed to produce. – Mr.Wizard Jul 12 '15 at 20:43
  • It should produce the same thing as combine[#& + Derivative[1]] produces. Namely: (#1 + Derivative[1][#1]) & – Myridium Jul 13 '15 at 00:17
  • @ciao - I've updated my question; hopefully it better articulates what I'm looking for. – Myridium Jul 13 '15 at 00:37
  • Thanks for coming back to this question to help us both (mostly me) understand what exactly I was looking for. This solution meets my needs, cheers. – Myridium Jul 13 '15 at 06:26
  • @Myridium You are welcome. Thank you for having a good attitude about the temporary "on hold" status, and thanks also for the Accept. – Mr.Wizard Jul 13 '15 at 06:33
2

Is this near to what you are looking for?

    ClearAll[f, g, h]

    f1[s__] := Total@Take[{s}, 3];

    f2[s__] := Times @@ Take[{s}, 2];

    f3[s__] := {s}[[1]] {s}[[3]] - {s}[[2]] {s}[[4]];

    expr = Through[(f + g + h)[##]] &

    w = expr /. {f -> f1, g -> f2, h -> f3};

    w[5, 6, 7, 8]


(* 35 *)
Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
John McGee
  • 2,538
  • 11
  • 15
  • Thanks, but I'm afraid not; you've appended & to the end of In[108] and this delays evaluation of Through until w is supplied with arguments. I want expr to evaluate as an expression involving Slots. – Myridium Jul 11 '15 at 13:55
2

I don't believe you can do it with Through[ ]. The following works:

f = {#1, #2} &;
g = Sin[{#1, #2}/#3] &;
h = Cos[{#1 + #2, #3 + #4 + #5}] &;
expr = Evaluate[(Plus @@ (First /@ {f, g, h}))] &

(* {Cos[#1 + #2] + Sin[#1/#3] + #1, Cos[#3 + #4 + #5] + Sin[#2/#3] + #2}  & *)

expr[a, b, c, d, e]
(* {a + Cos[a + b] + Sin[a/c], b + Cos[c + d + e] + Sin[b/c]} *)
Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
  • Thanks, this does work for functions written with Slots, but not with another pure function such as: Function[{a}, a^2] – Myridium Jul 11 '15 at 14:42
  • In reality, I am dealing with compositions of pure functions, and this approach of taking the First part doesn't work then. – Myridium Jul 11 '15 at 14:54
  • @Myridium You may expect all kind of weird things on that setup Function[{a}, {#1, a}] & – Dr. belisarius Jul 11 '15 at 16:25
  • Fair point, but for my purposes, I would like to evaluate the functions anyway with slots as their arguments. – Myridium Jul 11 '15 at 16:28
2

Could this work for you?

f = # &;
g = #^2 &;
h = (-2 #1 + #3) &;

totalfunc = (f + g + h) /.
   f_[fl__Function] :>
       Module[{arg = {fl} /. Function -> List, int},
           int = f @@ arg;
           Function @@ (Evaluate[int])
       ]

totalfunc[6, 3, 3] ==> 33

The replacement rule is rather general.

EDIT:

In the case you mention a slight modification is necessary:

f = #1 & ; 
g = #1^2 & ; 
h = #1^3 + #3^3 & ; 
k = Function[{a}, a^4]; 
j = Function[{a, b}, a^5 + b^5];


ClearAll[makevarlist];
makevarlist[f_Function] :=
  Module[{slots},
   If[Length[f] > 1,
    slots = Slot /@ Range[Length[First[f]]];
    {slots, Last[f] /. Thread[First[f] -> slots]}
    ,
    {{}, List @@ f}
   ]
  ];


totalfunc = (f + g + h + k + j) /.
  f_[fl__Function] :>
   Module[{arg, int, vars},
    arg = makevarlist /@ {fl};
    int = f @@ (Last /@ arg);
    Function @@ (Evaluate[int])
   ]

totalfunc => #1^5 + #1^4 + #1^3 + #1^2 + #1 + #2^5 + #3^3 &

user8074
  • 1,592
  • 8
  • 7
2

My quick-n-dirty take on this:

mergef = Module[{ds = Symbol /@ ("s" <> ToString@# & /@ Range@100), fs = ##, rf},
    rf = Plus@Through[fs[Sequence @@ ds]];
    Function @@ {Take[ds, Max[Position[ds, #] & /@ 
                 Cases[rf, Alternatives @@ ds, Infinity]]], rf}] &;

(* do some stuff *)
f = # &;
g = Function[{a}, a^2];
h = (-2 #1 + #3) &;
k = #6*10 &;

resfn = mergef[f, g, h, f, g, h, k]
resfn[2, 3, 4, 5, 6, 7]

(*
Function[{s1, s2, s3, s4, s5, s6}, -2 s1 + 2 s1^2 + 2 s3 + 10 s6]
82
*)

mergef[resfn, resfn][2, 3, 4, 5, 6, 7]

(* 164 *)

(* using *belisarius*' functions *)

mergef[f1, f2, f3][5, 6, 7, 8]

(* 35 *)
ciao
  • 25,774
  • 2
  • 58
  • 139
  • Am I mistaken, or does this work only up to 100 slots? If that is the case, then I may as well use expr = Through[(f+g+h)@@Slot/@Range@100] – Myridium Jul 12 '15 at 08:40
  • @Myridium: yes, that's just a static choice I made - one would question the sanity of writing a function with that many arguments, but can be set to whatever... as far as your comment code, try that with e.g. the example I commented to Mr. W... in any case, as noted this was barfed out in a minute or two, to perhaps give some ideas, nothing more. – ciao Jul 12 '15 at 08:46