Since I put something together to do this this past fall, I guess I should throw my hat in the ring, too. I think it is close to water tight, but I can't be sure.
First, we need to determine what the variables in the expression are
Clear[GetVariables]
SetAttributes[GetVariables, HoldFirst];
GetVariables[expr_, f_:Identity, excludedContexts:{__String}:{"System`"}]:=
Cases[Unevaluated[expr],
a_Symbol /;
!( MemberQ[excludedContexts, Context[a]]
|| MemberQ[Attributes[a], Locked | ReadProtected]
) :> f[a],
{0, Infinity}
]//DeleteDuplicates
Unlike the others, it provides flexibility in specifying which Contexts
are to be excluded, and removes from consideration both Locked
and ReadProtected
symbols. As a flaw, it only looks at symbols, so it won't distinguish between Subscript[a,1]
and Subscript[a,2]
. The second parameter here is special, it allows us to put wrappers, such as Hold
, around an accepted symbol to prevent its execution.
Second, we need to use it:
ClearAll[MakeFunction]
Options[MakeFunction]={VariableList->Automatic};
SetAttributes[MakeFunction, HoldFirst];
(* This first form allows pure functions to be used *)
MakeFunction[afcn_Function, opts:OptionsPattern[]]:= afcn
MakeFunction[fexpr_, opts:OptionsPattern[] ]:=
Module[{vars},
vars = If[OptionValue[VariableList]===Automatic,
(* GetVariables returns {Hold[x_] ..} we want Hold[{x_ ..}] *)
Distribute[Sort[GetVariables[fexpr, Hold]], Hold],
OptionValue[Automatic, Automatic, VariableList, Hold]
];
Function @@ Join[vars, Hold[fexpr]]
]
There are a couple things to notice here. First, it allows for pure functions to be passed to it. This is merely for convenience as it makes it more broadly applicable. Second, the option VariableList
allows the user to specify what the variables actually are because if we know them already, we might as well use them. This has the added benefit of allowing the user to change the order of the parameters which defaults to lexical sorting.
Through @ (MakeFunction /@ {x^2, Sin[x y^2], x + I y})[3, 4]
(* {9, Sin[48], 3 + 4 I} *)
Through @ (MakeFunction[#, VariableList -> {y, x}] & /@ {x^2, Sin[x y^2], x + I y})[3, 4]
(* {16, Sin[36], 4 + 3 I} *)
Function
if you don't know in which order the variables may end up? I mean, they are sorted alphabetically, so if I swapped x and y the structure of the original function would be the same, but the resultingFunction
would behave differently. $\endgroup$func_[vars__]
. I wouldn't be usingfunc
by itself (despite the wording of my question, the pure function alone is not the final goal) $\endgroup$