29
$\begingroup$

I need a function which can take an expression and return a pure function based on the symbols in the expression. The symbols might have values so must be protected from evaluation. It is probably easiest to give an example:

I would like to evaluate something like

x = y = 1;
extractPureFunction[Sin[Pi x^2] + y]

and obtain

Function[{x, y}, Sin[Pi x^2] + y]

or

(Sin[Pi #1^2] + #2) &

Any ideas?

$\endgroup$
2
  • 8
    $\begingroup$ Just curious: how would you ever going to use the 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 resulting Function would behave differently. $\endgroup$ Aug 30, 2012 at 21:56
  • $\begingroup$ @SjoerdC.deVries, good question! Essentially I want to be able to transform an expression to a form which matches the pattern func_[vars__]. I wouldn't be using func by itself (despite the wording of my question, the pure function alone is not the final goal) $\endgroup$ Aug 31, 2012 at 13:05

6 Answers 6

18
$\begingroup$

What about taking the symbols not in heads that haven't got the NumericFunction or Constant or Protected attribute (thanks @OleksandrR) and that are in the Global` context? The condition can easily be tweaked, and also one can easily add options on attributes, contexts, or extra symbols to be included or always excluded of argmuents

SetAttributes[{extractPureFunction, condition}, HoldFirst];
condition[i_Symbol] := 
  FreeQ[Attributes@i, NumericFunction | Constant | Protected] && 
   Context@i == "Global`";


extractPureFunction[expr_] := 
 Union@Cases[Unevaluated@expr, 
     i_Symbol?condition :> Hold[i], {0, Infinity}]~Thread~Hold /. 
  Hold[vars_] :> Function[vars, expr]


extractPureFunction[Sin[x] x + Pi + y - Total@Through@{Tan, ArcTan}[E]]

Function[{x, y},Sin[x] x + [Pi] + y - Total[Through[{Tan, ArcTan}[E]]]]

Note, this doesn't respect inner scoping constructs so variables from inner Functions or Modules for example would get listed as arguments

$\endgroup$
8
  • 2
    $\begingroup$ I was puzzling over whether to post this myself, but I forgot to consider the Attributes--good catch. Also, your use of Thread is nice (I had used a {Flat, HoldAll} function). $\endgroup$ Aug 30, 2012 at 17:10
  • $\begingroup$ Thanks @OleksandrR. This kind of manipulations are never easy, at least for me, I don't do them often $\endgroup$
    – Rojo
    Aug 30, 2012 at 17:27
  • $\begingroup$ Oh, and also: setting Heads -> True for Cases might be a useful addition, depending on how you want things like Derivative[x][y] to behave. $\endgroup$ Aug 30, 2012 at 17:34
  • $\begingroup$ Thinking a bit more about the attributes: isn't it enough just to check for Protected? If something has that then it can't have been meant as a variable in the first place. Constant, on the other hand, just means its derivative is zero, which may or may not be relevant here. $\endgroup$ Aug 30, 2012 at 19:45
  • $\begingroup$ @OleksandrR. that's probably a good idea. However, there's a Cases[Names["System`*"], i_ /; FreeQ[Attributes@i, Protected]] long list of unprotected System symbols to think about. Edited to add that check but I'm not sure if the others are redundant $\endgroup$
    – Rojo
    Aug 30, 2012 at 19:49
16
$\begingroup$

Well, since you defined x=y=1, evaluation semantics will make it very difficult to get at them inside your mathematical expression.

The general issue is one of extracting the variables. I show a way to go about that here. With getAllVariables as defined therein, one can then do as below.

extractPureFunction[expr_] := Module[{vars, func},
  vars = Cases[getAllVariables[expr],_Symbol];
  func[vars, expr] /. func -> Function]

Test:

In[38]:= extractPureFunction[Sin[Pi t^2] + w]

(* Out[38]= Function[{w, t}, w + Sin[Pi*t^2]] *)
$\endgroup$
7
  • $\begingroup$ extractPureFunction[Sin[Pi t^2] + w[[1]]] $\endgroup$ Aug 30, 2012 at 16:21
  • $\begingroup$ @Verde Fixed, more or less. That is to say, it will now only accept "variables" that are symbols. I'm sure it's still not bullet proof. $\endgroup$ Aug 30, 2012 at 16:35
  • $\begingroup$ Sounds fair enough :) $\endgroup$ Aug 30, 2012 at 16:50
  • $\begingroup$ I like your getAllVariables. Shame that we need something like Internal`LocalizedBlock to actually use most of those expressions as variables, though... $\endgroup$ Aug 30, 2012 at 17:41
  • $\begingroup$ Thanks Daniel. Unfortunately I really need something that works when the variables have values. Your getAllVariables is definitely one for the toolbag though. $\endgroup$ Aug 31, 2012 at 13:06
8
$\begingroup$

Here's my approach, which is similar to Rojo's in some ways. I'm taking the simple approach that any symbol not in System` is a user variable. (Adjust the condition as needed.)

SetAttributes[extractPureFunction, HoldAll]
SetAttributes[heldVariables, HoldAll]

heldVariables[e_] := 
 Thread[Union[
   Cases[HoldForm[e], 
    s_Symbol /; Context[s] =!= "System`" :> Hold[s], Infinity]], Hold]

extractPureFunction[e_] := 
 ReleaseHold[Function @@@ Thread[{heldVariables[e], Hold[e]}, Hold]]

Here's the test case:

x = y = 1;
extractPureFunction[Sin[Pi x^2] + y]

(*  ==> Function[{x, y}, Sin[\[Pi] x^2] + y]  *)
$\endgroup$
0
8
$\begingroup$

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} *)
$\endgroup$
2
  • $\begingroup$ @SimonWoods thanks. Admittedly, it isn't entirely my work, Leonid looked at it a while back and had some suggestions. $\endgroup$
    – rcollyer
    Aug 31, 2012 at 14:36
  • $\begingroup$ @SimonWoods it was needed to create a function which allowed me to plot along an arbitrary $\mathbb{R}^N \to \mathbb{R}$ function using Plot, and I desperately needed the ability to make an expression executable. $\endgroup$
    – rcollyer
    Aug 31, 2012 at 14:40
5
$\begingroup$

I'm sure this is far from watertight, but it seems to work for the expression I've tried. The hard bit was preventing Mathematica from evaluating the symbols prematurely.

toFunction[exp_] := Module[{exp1, syms},
  exp1 = ToExpression[exp, InputForm, Hold];
  syms = SymbolName /@ Pick[#, Not[NumericQ[Unevaluated[#]]] & /@ #] &@
    ReleaseHold[{Unevaluated /@ Level[exp1, {-1}, Hold]}];
  ToExpression["Function[ {" <> StringJoin[Riffle[syms, ","]] <> "}," <> exp <> "]"]]

Example:

a = 2;
ff = toFunction["Sin[abc a+b+Pi/5]^4-5"]

(* Function[{abc, a, b}, Sin[abc a + b + \[Pi]/5]^4 - 5] *)

Edit: I missed the fact that the argument was given as an expression and not as a string. In that case you could do something like

SetAttributes[toFunction, HoldAll]
toFunction[exp_] := Module[{syms},
  syms = SymbolName /@ Pick[#, Not[NumericQ[Unevaluated[#]]] & /@ #] &@
    ReleaseHold[{(Unevaluated /@ Level[Hold[exp], {-1}, Hold])}];
  ToExpression[
   "Function[ {" <> StringJoin[Riffle[syms, ","]] <> "}," <> 
    ToString[Unevaluated[exp], InputForm] <> "]"]]
$\endgroup$
1
  • $\begingroup$ Lateral thinking, I like it! The code needs a Union or DeleteDuplicates to catch multiple occurrences of the same symbol. $\endgroup$ Aug 31, 2012 at 13:08
5
$\begingroup$

Here's my approach. It works by picking out only the non-heads and then filtering out the built-in constants like π, E and numbers.

ClearAll[toPureFunction]
SetAttributes[toPureFunction, HoldAll]
toPureFunction[expr_] := With[{constantQ = MemberQ[Attributes[#], Constant] &}, 
    Module[{vars, func}, 
        vars = Quiet[
            Cases[
                HoldForm@expr // Level[#, {-1}, Unevaluated] &, 
                x_Symbol?(OwnValues[#] =!= {} || ! constantQ[#] &) :> Hold@x], 
            OwnValues::sym];

        Quiet[Function[Evaluate@DeleteDuplicates@vars, expr] // ReleaseHold, Function::flpar]
    ]
]

Here's the output on some of the examples used in the question and other answers:

toPureFunction[Sin[π x^2] + y]
(* Function[{x, y}, Sin[π x^2] + y] *)

toPureFunction[Sin[x] x + Pi + y - Total@Through@{Tan, ArcTan}[E]]
(* Function[{x, y}, Sin[x] x + π + y - Total[Through[{Tan, ArcTan}[E]]]] *)
$\endgroup$
1
  • $\begingroup$ Nice. I must say I like the pragmatism of Quieting the warnings rather than doing code gymnastics to avoid them. A minor point is that your approach removes any Hold in the original expression. $\endgroup$ Aug 31, 2012 at 13:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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