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
With
and the condition, but within the context of local rules. Since the condition isTrue
, it forced theeval
variable 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. $\endgroup$ – Leonid Shifrin Aug 4 '17 at 12:40