Using predefined expressions as free variables in a pure function

Why doesn't it work?

Evaluate only has effect at the first level in a held expression.

{Hold[1 + 1], Hold[Evaluate[1 + 1]], Hold[f[1 + 1]]}
(* {Hold[1 + 1], Hold[2], Hold[f[1 + 1]]} *)

Why does Evaluate only work at the first level?

Because the evaluator does not look for exceptions beyond the first level inside an expression having a head with HoldAll. If it did, it may potentially spend a lot of time scanning large expressions.

Read about evaluation:

  • http://reference.wolfram.com/language/tutorial/Evaluation.html

  • http://reference.wolfram.com/language/tutorial/EvaluationOfExpressionsOverview.html

What's the solution?

Typically one has to use Replace or ReplaceAll.

Function[x, y] /. y -> x^2
(* Function[x, x^2] *)

All this falls under the label of code-generation, a difficult topic where things may go wrong all too easily due to how Mathematica implements scoping using variable renaming.

Example of how things may go wrong:

Can't we just use With instead of Replace? Let's try:

With[{y = x^2}, Function[x, y]]

(* Function[x$, x^2] *)

Oops! The function argument got renamed from x to x$ to prevent a name collision. At the same time it "broke" our function. This is the reason why Replace and ReplaceAll are better than With for such purposes.

But is Replace bulletproof? Not at all, unfortunately. When doing such a transformation, we might want to localize y. Let's see what happens.

Module[{y},
 Function[x, y] /. y -> x^2
]

(* Function[x$, x^2] *)

Oops! The renaming kicked in again. (Note: Block won't rename here but it's not always a good replacement to Module.)

Doing such things can also lead to situations where SetSystemOptions["StrictLexicalScoping" -> True] may make a significant difference (may fix or break things). This is why Stan Wagon's FindRoots2D function won't work (as posted on this site) if strict lexical scoping is turned on.

Sometimes we can use a trick like this to avoid variable renaming:

Module[{y}, Function @@ Hold[x, y] /. y -> x^2]
(* Function[x, x^2] *)

Writing Function @@ Hold[...] instead of Function[...] prevents the renaming (for better or worse).

To sum up, code generation seems to be quite difficult in Mathematica. Caution is advised. Now if someone could prove me wrong, I would be very happy ... Looking forward to other answers!


Your issue is quite simple and fundamental: you try to use lexical scoping (SetDelayed and Function) as it would be dynamic scoping (Block). So your problem can be solved easily by outsourcing the dynamic scoping to Block with minimal modification of your original code:

MWE1 = b x^2 + a y^2 + 3 + δ;
Dx = D[MWE1, x];
Dy = D[MWE1, y];
MWF[aa_, bb_] = 
 Function[{δδ}, 
  Block[{δ = δδ, a = aa, b = bb}, 
   MWE1 /. FindRoot[{Dx == 0, Dy == 0}, {{x, -3, 3}, {y, -3, 3}}, 
     AccuracyGoal -> Infinity]]]

test3 = MWF[1, 1];
test3[1]

NDSolve`ScaledVectorNorm::urange: The scaling vector has a zero component, so the zero absolute tolerance leads to an unbounded value.

4.

Recommended reading:

  1. Plot using With versus Plot using Block

  2. What are the use cases for different scoping constructs?


Update

I just realized that it is possible to achieve what you want by means of anonymous pure function # &:

MWE1 = b x^2 + a y^2 + 3 + δ;
Dx = D[MWE1, x];
Dy = D[MWE1, y];
MWF[a_, b_] = Function[{δ},
    #1 /. FindRoot[{#2 == 0, #3 == 0}, {{x, -3, 3}, {y, -3, 3}}, AccuracyGoal -> Infinity]
    ] &[MWE1, Dx, Dy]

test3 = MWF[1, 1];
test3[1]

This method utilizes the fact that anonymous pure functions do not rename variables in the nested scoping constructs.


It can be done, but I suspect you will find it a bit more tricky than you expected. Here is one way to do it. Note that I don't introduce a variable named δ. It is easier to work with Slot ( # ).

{a, b, x, y} = Range[4]; (* to ensure scope conflicts assert themselves *)

MWF[a_, b_] =
  Block[{MWE1, Dx, Dy, a, b, x, y},
    MWE1 = b x^2 + a y^2 + 3 + #;
    Dx = D[MWE1, x];
    Dy = D[MWE1, y];
    With[{f = MWE1, dx = Dx, dy = Dy},
      Function[f /.
        FindRoot[{dx == 0, dy == 0}, {{x, -3, 3}, {y, -3, 3}}, 
          AccuracyGoal -> ∞]]]]
3 + b x^2 + a y^2 + #1 /. 
  FindRoot[{2 b x == 0, 2 a y == 0}, {{x, -3, 3}, {y, -3, 3}}, 
    AccuracyGoal -> ∞] &
testF = MWF[1, 1]
3 + 1 x^2 + 1 y^2 + #1 /. 
  FindRoot[{2 x == 0, 2 y == 0}, {{x, -3, 3}, {y, -3, 3}}, 
    AccuracyGoal -> ∞] &
Block[{x, y}, testF[1]]

msg

4.