Introduction
I'm not aware of any way to hold evaluation of arguments in expression of form Inactive[f][arguments]
, which is how inactivated expressions look like. What we can do, to prevent evaluation of arguments, is to use a symbol with appropriate Hold...
attribute, instead of Inactive[f]
expression. This symbol should have no DownValues
that could evaluate when arguments are passed to it.
Below I present two approaches to this problem. First is based on custom (in)activate functions, creating "dummy" symbols on the fly, for each inactivated symbol with Hold...
attribute. Second attaches special inactivation behavior to specific symbols, which causes them to evaluate to custom inactive heads.
Basic examples
When we have a function with a Hold...
attribute.
ClearAll[f]
SetAttributes[f, HoldFirst]
f[x_, y_] := Hold[x, y]
f[1 + 1, 2 + 2]
(* Hold[1 + 1, 4] *)
Ordinary inactivation of this function causes evaluation of its arguments:
Inactivate[f[1 + 1, 2 + 2], f]
% // FullForm
% // Activate
(* f[2, 4] *)
(* Inactive[f][2, 4] *)
(* Hold[2, 4] *)
so expression after inactivation and activation is different than if it would evaluate without inactivation involved.
With first, presented below, approach one can do:
holdingInactivate[f[1 + 1, 2 + 2], f]
% // FullForm
% // holdingActivate
(* f[1 + 1, 4] *)
(* inactive`Global`f[Plus[1, 1], 4] *)
(* Hold[1 + 1, 4] *)
with second approach:
setHoldingInactivation[f];
Inactivate[f[1 + 1, 2 + 2], f]
% // FullForm
% // Activate
(* f[1 + 1, 4] *)
(* holdingInactiveHoldFirst[Plus[1, 1], 4, Hold[Inactive[f]]] *)
(* Hold[1 + 1, 4] *)
Implementation
Let's start with some helper symbols used in both approaches.
ClearAll[symPatt, holdAttrs, hasHoldAttrQ, getHoldAttr]
symPatt = Except[HoldPattern@Symbol[___], _Symbol];
holdAttrs = HoldFirst | HoldRest | HoldAll | HoldAllComplete;
hasHoldAttrQ = Function[, MemberQ[Attributes[#], holdAttrs], HoldFirst];
getHoldAttr = Function[, FirstCase[Attributes[#], holdAttrs], HoldFirst];
Dummy "inactive" symbols
In this approach we define custom holdingInactivate
, holdingActivate
and ignoringHoldingInactive
functions that should be used instead of built-in Inactivate
, Activate
and IgnoringInactive
.
holdingInactivate
inactivates expression and replaces each inactive symbol, that has Hold...
attribute, with symbol specially defined in inactive`
context. This special symbol has same Hold...
attribute as replaced one, and is formatted as inactive replaced symbol.
holdingActivate
activates expression and replaces symbols from inactive`
context with original symbols.
ignoringHoldingInactive
returns IgnoringInactive
expression with certain symbols replaced by Alternatives
of original symbol and its dummy "inactive" counterpart, so returned pattern will match both active and inactive versions of expressions.
We start with some helper functions.
ClearAll[
$inactiveContext, inactiveSymbolQ, toInactiveSymbol, fromInactiveSymbol,
postprocessInactiveBoxes, defineInactiveSymbol, $inactivateExclusions,
$inactivateExclusionsHeld, $basicInactivePatternRules,
inactivePatternReplace
]
$inactiveContext = "inactive`";
inactiveSymbolQ[s:symPatt] := StringMatchQ[Context[s], $inactiveContext <> "*"]
inactiveSymbolQ[expr_] = False;
toInactiveSymbol[inactSym:symPatt?inactiveSymbolQ] := inactSym
toInactiveSymbol[s:symPatt] :=
Symbol[$inactiveContext <> Context[s] <> SymbolName@Unevaluated[s]]
fromInactiveSymbol[inactSym:symPatt?inactiveSymbolQ] :=
Symbol@StringJoin[
StringDrop[Context[inactSym], StringLength[$inactiveContext]],
SymbolName@Unevaluated[inactSym]
]
fromInactiveSymbol[s:symPatt] := s
SetAttributes[postprocessInactiveBoxes, HoldAllComplete]
postprocessInactiveBoxes[_, hISym_][
RowBox[{TemplateBox[tbArg_, "InactiveHead", opts___], "[", args___, "]"}]
] :=
With[{tooltip = ToString[Unevaluated[hISym], InputForm]},
RowBox[{
InterpretationBox[
TemplateBox[tbArg, "InactiveHead", Tooltip -> tooltip, opts],
hISym
],
"[", args, "]"
}]
]
postprocessInactiveBoxes[expr_, _][boxes_] := InterpretationBox[boxes, expr]
defineInactiveSymbol[h:symPatt /; Not@inactiveSymbolQ[h]] :=
With[{holdAttr = getHoldAttr[h]},
With[{hISym = toInactiveSymbol[h]},
ClearAll[hISym];
SetAttributes[hISym, holdAttr];
hISym /: MakeBoxes[expr : hISym[args___], form_] :=
postprocessInactiveBoxes[expr, hISym]@
MakeBoxes[Inactive[h][args], form];
hISym
] /; Not@MissingQ[holdAttr]
]
$inactivateExclusions =
Alternatives @@ Replace[Developer`$InactivateExclusions, {
{sym_, "Symbol"} :> sym,
{sym_, "Expression"} :> Blank[sym]
}, 1];
$inactivateExclusionsHeld =
Alternatives @@ Cases[Developer`$InactivateExclusions,
{
sym_ /; MemberQ[Attributes[sym], HoldAll | HoldAllComplete],
"Symbol"
} :> Blank[sym]
];
$basicInactivePatternRules = {
excl:$inactivateExclusions :> excl,
h:symPatt /; Not@inactiveSymbolQ[h] :>
With[{inactSym = toInactiveSymbol[h]}, h | inactSym /; True]
};
inactivePatternReplace[expr_] :=
Quiet[
Unevaluated[expr] /. {
(h : Condition | PatternTest | Repeated)[patt_, rest___] :>
With[{replaced = inactivePatternReplace[patt]},
h[replaced, rest] /; True
],
Verbatim[Pattern][name_, patt_] :>
With[{replaced = inactivePatternReplace[patt]},
Pattern[name, replaced] /; True
],
Verbatim[Verbatim][verb_] :>
With[
{replaced =
Unevaluated[verb] /. $basicInactivePatternRules
},
Verbatim[replaced] /; True
],
(bl : Blank | BlankSequence | BlankNullSequence)[
h:symPatt /; Not@inactiveSymbolQ[h]
] :>
With[{inactSym = toInactiveSymbol[h]},
bl[h] | bl[inactSym] /; True
],
Sequence @@ $basicInactivePatternRules
},
RuleDelayed::rhs
]
SetAttributes[{
inactiveSymbolQ, toInactiveSymbol, fromInactiveSymbol,
defineInactiveSymbol, inactivePatternReplace
}, HoldFirst]
Now three "public" functions.
ClearAll[holdingInactivate, holdingActivate, ignoringHoldingInactive]
SetAttributes[holdingInactivate, HoldFirst]
holdingInactivate[expr_, patt_:_, opts:OptionsPattern[Inactivate]] :=
Inactivate[Hold[expr], patt, opts] //.
Inactive[h:Except[Except[_Symbol] | Except[patt]]][args___] :>
With[{hISym = defineInactiveSymbol[h]},
hISym[args] /; MatchQ[hISym, symPatt]
] //
ReleaseHold
holdingActivate[expr_, patt_:_, opts:OptionsPattern[Activate]] :=
Activate[expr, patt, opts] /. h:patt?inactiveSymbolQ :> fromInactiveSymbol[h]
ignoringHoldingInactive[expr_] :=
IgnoringInactive[expr] /. {
excl:$inactivateExclusionsHeld | $inactivateExclusions :> excl,
(h:symPatt /; hasHoldAttrQ[h] && Not@inactiveSymbolQ[h])[args___] :>
HoldPattern@h[args]
} // Evaluate // inactivePatternReplace
Usage example:
ClearAll[f, g, h]
SetAttributes[f, HoldFirst]
SetAttributes[g, HoldAll]
testExpr = f[1 + 1, g[1 + 1, 1 + 1], 2, f[1 + 1]][h[2], 2, f[1 + 1]]
inactiveTestExpr = holdingInactivate[Evaluate[%], f | g | h]
% // FullForm
% // holdingActivate

Pattern wrapped with ignoringHoldingInactive
will match both active and inactive versions of same expression:
MatchQ[testExpr, ignoringHoldingInactive[testExpr]]
(* True *)
MatchQ[inactiveTestExpr, ignoringHoldingInactive[testExpr]]
(* True *)
It can be also used to manipulate inactive expression:
inactiveTestExpr /. {
ignoringHoldingInactive[f[arg : 1 + _]] :> Hold[arg],
ignoringHoldingInactive[gExpr_g] :> 5 + gExpr
}

Attaching special inactivation behavior to symbols
In this approach we attach special behavior to some of symbols that are supposed to be inactivated. Inactivation and activation is performed using built-in Inactivate
and Activate
.
We set special UpValues
, for e.g. f
symbol, causing Inactive[f][args]
to evaluate to holdingInactive...[args, Hold@Inactive[f]]
, where holdingInactive...
is head with same Hold...
attribute as f
. We keep f
itself in last argument of holdingInactive...
, this way expression can be appropriately formatted and, when activated, can automatically evaluate to original f[args]
.
Since special behavior, during both inactivation and activation, depends on evaluation, Inactive[f][args]
expressions will not be replaced by holdingInactive...
expression if it's inside some holding wrapper itself. This will not cause any problems with evaluation of f
's arguments (since it's in holding wrapper they will not evaluate), but under the hood inactive expression can be slightly inconsistent and can contain both Inactive[f][args]
and holdingInactive...[args, Hold@Inactive[f]]
, which might be inconvenient when manipulating inactive expression. When part of inactive expression is allowed to evaluate, then held, then activated, it may happen that we end up with expression containing holdingInactive...
that will remain there until it's allowed to evaluate.
ClearAll[holdingInactive, setHoldingInactivation]
(* Define four holdingInactive... functions one for each Hold... atribute. *)
Scan[
With[{head = Symbol["holdingInactive"<>ToString[#]]},
ClearAll[head];
SetAttributes[head, #];
head[args___, Hold[h:Except@Inactive[_]]] := h[args];
head /: MakeBoxes[expr:head[args___, Hold[h:Inactive[_]]], form_] :=
InterpretationBox[#, expr]&@MakeBoxes[h[args], form];
holdingInactive[#] = head
]&,
holdAttrs
]
SetAttributes[setHoldingInactivation, Listable]
setHoldingInactivation[h_Symbol] :=
With[{holdAttr = getHoldAttr[h]},
With[{holdingInactiveFunc = holdingInactive[holdAttr]},
h /: Inactive[h] =
Function[,
holdingInactiveFunc[##, Hold@Inactive[h]],
HoldAllComplete
];
h
] /; Not@MissingQ[holdAttr]
]
Usage example:
ClearAll[f, g, h]
SetAttributes[f, HoldFirst]
SetAttributes[g, HoldAll]
setHoldingInactivation[{f, g}]
(* {f, g} *)
f[1 + 1, g[1 + 1, 1 + 1], 2, f[1 + 1]][h[2], 2, f[1 + 1]]
Inactivate[Evaluate[%], f | g | h]
% // FullForm
% // Activate

SetAttributes[Inactive[f], HoldFirst]]
fails becauseSetAttributes
takesSymbol
as its first argument. $\endgroup$ – QuantumDot Jun 9 '16 at 15:16f
is deactivated, why should itsAttributes
be still active? Makes sense to me although the question how to workaroud that is interesting. $\endgroup$ – Kuba♦ Jun 9 '16 at 15:54Inactive
, e.g.myInactive[head_] := Internal`InheritedBlock[{head},DownValues[head]={}, (*code*)]
would work? $\endgroup$ – LLlAMnYP Jun 9 '16 at 16:02// Inactivate
doesn't suit you. You might also findDefer
useful. As I suggested in my previous comment, your desired behavior would appear to suppress only matching ofDownValues
patterns. This can probably be done differently. $\endgroup$ – LLlAMnYP Jun 9 '16 at 21:34f
'sDownValues
would be more appropriate? It'll keepf
's attributes, prevent it's evaluation and leave structure of expression unchanged. $\endgroup$ – jkuczm Jun 10 '16 at 11:02