Generally speaking, With can be used to inject into arbitrarily held expressions, for example:
With[{args = 2},
Hold @ Hold @ HoldComplete[args]
]
(* Out[1]= Hold[Hold[HoldComplete[2]]] *)
Attributes @ foo = HoldAll;
With[{args = {{x, _Real}}},
foo[args, x + 2]
]
(* Out[2]= foo[{{x, _Real}}, x + 2] *)
However, trying to do the same exact thing to inject inside Compile does not work:
With[{args = {{x, _Real}}},
Hold @ Compile[args, x + 2]
]
(* Hold[Compile[args, x + 2]] *)
Also, the only relevant attribute of Compile is HoldAll, which as shown in the first examples is not really a problem for this kind of injection.
Does this mean that Compile follows some special evaluation rules (even though it's not listed among the magic symbols)?
Using a Trott-Strzebonski-like trick, like shown for example here, does work:
With[{args = {{x, _Real}}},
Hold @ Compile[args, x + 2] /; True
][[1]]
(* Hold[Compile[{{x, _Real}}, x + 2]] *)
An extract from Leonid's explanation in the above linked answer explains why adding the Condition works:
The basic idea is to exploit the semantics of rules with local variables shared between the body of
Withand the condition, but within the context of local rules. Since the condition isTrue, it forced theevalvariable to be evaluated inside the declaration part ofWith(...)
This explanation does not however explain why the use of Compile in particular makes a difference, why does it prevent With from injecting into it, and why can we avoid this constraint adding a Condition?
Some more examples of functions showing or not showing this kind of behaviour:
With[{args = x}, Hold@Compile[args, 2]]
With[{args = x}, Hold@Function[args, 2]]
With[{args = x}, Hold@Module[args, 2]]
With[{args = x}, Hold@With[args, 2]]
With[{args = x}, Hold@Block[args, 2]]
(*
Out[1]= Hold[Compile[args, 2]]
Out[2]= Hold[Function[args, 2]]
Out[3]= Hold[Module[x, 2]]
Out[4]= Hold[With[x, 2]]
Out[5]= Hold[Block[x, 2]]
*)
Also, in at least some cases it matters whether there is a second argument:
With[{args = x}, Hold @ Compile[args]]
With[{args = x}, Hold @ Function[args]]
(* Out[1]= Hold[Compile[args]] *)
(* Out[2]= Hold[x &] *)
With[{args = x}, Hold @ Function[args]], my guess would be that a function with a single arg is treated as a slot-based pure function, which is not considered a scoping construct in the context of protection of local variables, since it does not use named variables (it uses slots instead). Which is why such protection is not there in this case. – Leonid Shifrin Aug 04 '17 at 12:40