9
$\begingroup$

Let f be defined as follows:

ClearAll[f];
SetAttributes[f, HoldFirst];

f[1+1]
(* f[1+1] *)

The 1+1 stays as is. But if I Inactivate f:

Inactive[f][1+1]
(* f[2] *)

it has the paradoxical effect of reactivating the held 1+1, making it evaluate to 2. How do I prevent Inactive from killing the attributes of f?

note: I am looking for a solution that solves the problem of losing attributes, not one of deactivating 1+1 by doing f[1+1] // Inactivate

$\endgroup$
7
  • $\begingroup$ Setting SetAttributes[Inactive[f], HoldFirst]] fails because SetAttributes takes Symbol as its first argument. $\endgroup$ – QuantumDot Jun 9 '16 at 15:16
  • 4
    $\begingroup$ Since f is deactivated, why should its Attributes be still active? Makes sense to me although the question how to workaroud that is interesting. $\endgroup$ – Kuba Jun 9 '16 at 15:54
  • 4
    $\begingroup$ One good question after another! +1 again. Perhaps rolling your own version of Inactive, e.g. myInactive[head_] := Internal`InheritedBlock[{head},DownValues[head]={}, (*code*)] would work? $\endgroup$ – LLlAMnYP Jun 9 '16 at 16:02
  • 2
    $\begingroup$ It would be helpful, if you could give some background on what you're trying to achieve, since its not quite clear, why // Inactivate doesn't suit you. You might also find Defer useful. As I suggested in my previous comment, your desired behavior would appear to suppress only matching of DownValues patterns. This can probably be done differently. $\endgroup$ – LLlAMnYP Jun 9 '16 at 21:34
  • 2
    $\begingroup$ It's not clear to me why you need this kind of behavior. Maybe temporary removal of f's DownValues would be more appropriate? It'll keep f's attributes, prevent it's evaluation and leave structure of expression unchanged. $\endgroup$ – jkuczm Jun 10 '16 at 11:02
8
$\begingroup$

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

print screen of inactivation with dummy symbols

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
}

print screen of replacement using ignoringHoldingInactive

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

print screen of result with custom inactive heads

$\endgroup$
1
  • $\begingroup$ This is very nice and far reaching with plenty of background info, +1 $\endgroup$ – LLlAMnYP Jun 10 '16 at 10:30
3
$\begingroup$

I think you are not properly aware of how the kernel is evaluating your expression and how the front-end is printing the result.

ClearAll[f]
SetAttributes[f, HoldFirst]
f[x_] := (x = 42)

Now if f were to see 1 + 1 as an argument in either evaluated or unevaluated form, it would complain.

f[1 + 1]

msg

f[2]

msg

but it doesn't complain when Inactive[f][1 + 1] is evaluated because f is treated as an inert symbol, never gets evaluated and never sees any argument at all.

Inactive[f][1 + 1] // FullForm

Inactive[f][2]

Just because the front-end pretty prints Inactive[f][2] as f[2] in an output cell doesn't mean that somehow f has lost it HoldFirst attribute.

Update

This is in response to the OP's comment.

Let's trace Inactive[f][Print[Hello]].

Trace[Inactive[f][Print[Hello]]]

trace

You see that Print[Hello] is first thing evaluated and returns Null. Then it looks as if f is applied to Null, but that's not the case, since if it were, the f[2] in the trace would be followed by its result, but there is no result.

The f[2] is produced because the front-end pretty prints HoldForm[Inactive[f][Null]] as f[2], which is the final returned value; f has not been evaluated at all, not with any argument, not even null, during the trace.

$\endgroup$
9
  • $\begingroup$ Thanks for your answer; but I don't understand it. I read your answer like 5 times and I still don't understand it. I'm just a dumb guy. Consider f[Print["Hello"]] vs Inactive[f][Print["Hello"]]. The first version doesn't print "Hello" because of f's HoldFirst attribute. But Inactive[f][Print["Hello"]] does print it because that attribute is suppressed by Inactive. How do I prevent this effect? $\endgroup$ – QuantumDot Jun 9 '16 at 17:18
  • $\begingroup$ @QuantumDot. No, in the case of Inactive[f][Print["Hello"]], the f is not evaluated at all, not with any argument, If it were evaluated the argument it would receive would be Null, which is what Print["Hello"] evaluates to. What you see printed is a side effect not the value of Print["Hello"]. $\endgroup$ – m_goldberg Jun 9 '16 at 17:24
  • $\begingroup$ @QuantumDot. I have edited the answer. I hope it will be a little clearer now. $\endgroup$ – m_goldberg Jun 9 '16 at 17:28
  • $\begingroup$ I really feel, you're insistent on sidestepping the actual question. If f was evaluated, it would receive Print["Hello"] as its argument, because symbols with attribute HoldFirst are passed their argument unevaluated. It seems pretty clear, that Inactive[f][1+1] // Activate is not equivalent to f[1+1] and I can see how this behavior may be undesirable. $\endgroup$ – LLlAMnYP Jun 9 '16 at 21:33
  • $\begingroup$ @LLlAMnYP. Sure I'm sidestepping. I have to. With the form Inactive[f][...], which is basis for the OP question, there is no way f can receive anything as its argument. I'm trying to make the OP see that. $\endgroup$ – m_goldberg Jun 9 '16 at 21:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.