0
$\begingroup$

This might be a very basic question, so apologies in advance, but I couldn't find a direct solution.

Suppose I have a module that takes function(s) and expression(s) as its arguments. For the sake of brevity, let's say it's a one-liner

testModule[{func0_, a0_}] :=  Module[{func = func0, a = a0}, Integrate[func[x, a], {x, -1, 1}]]

and I want to create a list of testModule outputs for a bunch of choices of arguments. I can brute-force this by explicitly defining the functions and then a vector of arguments for testModule, say,

f1[x_, a_] := a^2 + x^2
f2[x_, a_] := a - x
f3[x_, a_] := a + x
f4[x_, a_] := Exp[a*x]
vFunctions = {{f1, 1}, {f2, 2}, {f3, 1}, {f4, a}}

and get the result I want with

Table[testModule[vFunctions[[i]]], {i, 1, Length[vFunctions]}]

However, let's say that, for some reason, it's easier for me to construct the argument array with just expressions, keeping in mind that (with the example module from above) it's implied that in each element of the vector, the first element of each element is supposed to be a function

vExpressions =  Transpose[{{a^2 + x^2, a - x, a + x, Exp[a*x]}, {1, 2, 1, a}}]

Can I construct the table by turning the expressions into functions when calling testModule? Something like

Table[testModule[{With[{func0[x_, a_] := vExpressions[[i]][[1]]}, func0], vExpressions[[i]][[2]]}], {i, 1, Length[vExpressions]}]

does not work (because With only allows assigning symbols,

With::lvset: Local variable specification {func0[x_,a_]:=vExpressions[[i]][[1]]} contains func0[x_,a_]:=vExpressions[[i]][[1]], which is an assignment to func0[x_,a_]; only assignments to symbols are allowed.

and so on), and I couldn't come up with a different way of doing this. Is there a way to achieve something like this, or is it just a bad idea?

$\endgroup$
3
  • $\begingroup$ What you want to do can be dangerous. What guarantee do you have that, say, x, does not have a value previously assigned to it before you evaluate all of this? $\endgroup$ Mar 7, 2018 at 13:04
  • $\begingroup$ You can turn an expression into a function using Function and the Inactive-framework. E.g. Activate[Inactive[Function][{x, a}, expr]] for any expression involving x and a. You may want to check that neither x nor a have values assigned, using ValueQ[x] etc. $\endgroup$
    – JEM_Mosig
    Mar 7, 2018 at 21:58
  • $\begingroup$ @JEM_Mosig ah, I've never seen that before, it seems incredibly handy, thank you so much! It does exactly what I wanted. And yes, I'll make sure x,a are unassigned, although for the particular calculation I'm doing I think it's always going to be the case. $\endgroup$
    – starless
    Mar 8, 2018 at 9:58

1 Answer 1

1
$\begingroup$

The issues in this question seem to arise frequently, and so I thought it might be worth analyzing them in some detail.

Avoid Gratuitous Modules

Here's the anti-pattern:

testModule[{func0_, a0_}] := 
  Module[{func = func0, a = a0}, Integrate[func[x, a], {x, -1, 1}]]

Module buys you nothing in this situation. Ironically, the only symbol that might need localization is x, but we'll return to that later. If you look at the DownValues for testModule, you'll see the use of HoldPattern and RuleDelayed, and so the arguments func0 and a0 are sufficiently protected. In the vast majority of situations, you can safely do just:

testModule[{func0_, a0_}] := Integrate[func0[x, a0], {x, -1, 1}]

Understand SetDelayed's HoldAll attribute

Setting aside the issues with trying to use SetDelayed in With, let's look at:

func0[x_, a_] := vExpressions[[i]][[1]]

No matter what definitions exist for vExpressions, the resulting DownValues for func0 will look like this:

{HoldPattern[func0[x_, a_]] :> vExpressions[[1]][[1]]}

(I've replace i with 1 because the original intent was to use i as a Table iterator.)

When we try to evaluate something like func0[5, 5], the evaluator will try to substitute 5 into the right-hand side wherever it finds x (and a). Since x (and a) don't exist in the right-hand side, that will be a no-op. Evaluation will continue and we'll end up with a^2 + x^2.

You can force the right-hand side to evaluate to the expression you want like this:

func0[x_, a_] := Evaluate[vExpressions[[1]][[1]]]

Consider ReplaceAll when starting with expressions

Let's define an alternate for testModule:

option1Test[expression_, assignment_] := Integrate[expression /. assignment, {x, -1, 1}]

We expect expression to be some sort of algebraic expression written in terms of a and x, and we expect assignment to be replacement rules. Given that, we need to alter your expressions a bit:

option1Expressions = 
  Thread[{{a^2 + x^2, a - x, a + x, Exp[a*x]}, Thread[Rule[a, {1, 2, 1, a}]]}]

{{a^2 + x^2, a -> 1}, {a - x, a -> 2}, {a + x, a -> 1}, {E^(a*x), a -> a}}

We can now feed this into our function:

option1Test @@@ option1Expressions

{8/3, 4, 2, (2*Sinh[a])/a}

Consider using formal symbols

The problem with the above is that our x and a symbols are exposed in the Global context. Any assignments we make to them will corrupt our results. You can play games with Hold/Unevaluated/Inactive and the like, as well as setting attributes for your function, but this becomes obnoxious quickly. So, just choose variables that cannot be (trivially) given definitions. You can do this yourself with your preferred symbols, or you can just use the built-in \[Formal*] system symbols, which are protected by default.

option2Expressions = 
  Thread[{{\[FormalA]^2 + \[FormalX]^2, \[FormalA] - \[FormalX], \[FormalA] + \[FormalX], Exp[\[FormalA]*\[FormalX]]}, Thread[Rule[\[FormalA], {1, 2, 1, a}]]}];
option2Test[expression_, assignment_] := Integrate[expression /. assignment, {\[FormalX], -1, 1}]

Avoid implicit coupling

The problem with the above is that our option2Test function assumes that \[FormalX] will be in the expression. We could make that coupling explicit by introducing another argument to the function that tells us what symbol we're integrating on. This might be nice if we were doing some processing upstream of this part of the workflow, but the way the situation is described, this introduces unnecessary effort. All of our expressions are in x, and so it feels verbose to repeat the x. It's not difficult, just a bit awkward:

option3Expressions = 
  Thread[{{\[FormalA]^2 + \[FormalX]^2, \[FormalA] - \[FormalX], \[FormalA] + \[FormalX], Exp[\[FormalA]*\[FormalX]]}, Thread[Rule[\[FormalA], {1, 2, 1, a}]], \[FormalX]}];
option3Test[expression_, assignment_, symbol_Symbol] := 
  Integrate[expression /. assignment, {symbol, -1, 1}]

Consider using Functions

Since you wanted Functions anyway, why not just start with them? It's not quite as readable, but it's not that bad:

option4Expressions = 
  Thread[{{#2^2 + #1^2 &, #2 - #1 &, #2 + #1 &, Exp[#2*#1] &}, {1, 2, 1, a}}];
option4Test[function_, parameter_] := 
  Integrate[function[\[FormalX], parameter], {\[FormalX], -1, 1}]

This is pretty good, and is probably where I'd stop for most situations, however...

Use Module to localize symbols

We still cannot guarantee that \[FormalX] hasn't been redefined. This is an appropriate use of Module:

option5Test[function_, parameter_] := 
  Module[{x}, Integrate[function[x, parameter], {x, -1, 1}]]

Use Contexts and attributes to further protect your private symbols

This is probably going too far for this question...

$\endgroup$

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.