Why Unevaluated doesn't work on unheld side of Rule/RuleDelayed?

Unevaluated is a function that I never truly understand, and here's one of the cases confusing me:

Unevaluated[1 + 1] -> 3
(* 2 -> 3 *)


Same thing happens on right side of Rule and left side of RuleDelayed (:>) i.e. Unevaluated affects none of the unheld sides of Rule/RuleDelayed:

2 -> Unevaluated[1 + 2]
(* 2 -> 3 *)
Unevaluated[1 + 1] :> 3
(* 2 :> 3 *)


Trace shows that Unevaluated does seem to work for once, but after that,Rule evaluates its left side again:

Unevaluated[1 + 1] -> 3 // Trace


No matter how many Unevaluated exists, Rule will tenaciously evaluate again and again, until the Unevaluateds are all killed:

Unevaluated@Unevaluated@Unevaluated@Unevaluated[1 + 1] -> 3 // Trace


My questions are:

1. Is this just a individual case i.e. a special behavior of Rule/RuleDelayed, or there's a class of function that shares the same behavior?

2. Why Rule/RuleDelayed is designed to behave like this? Is there any deep meaning?

• I assume that Unevaluated is only kept until Rule recieves its arguments (as the documentation states), but afterwards arguments are evaluated normally as Rule does not have attribute HoldAll or similar. Compare with f[x_] := x;f[Unevaluated[1 + 1]] which also returns 2. – István Zachar Mar 19 '16 at 15:37
• @IstvánZachar But for your case f[Unevaluated@Unevaluated[1 + 1]] will return Unevaluated[1 + 1]. – xzczd Mar 19 '16 at 15:41
• Oh, I see your point now. Then I guess this behaviour is specific for rules, i.e. entities that specify rewrite processes, necessary for the Replace family. Furthermore, Association behaves similar (while DirectedEdge does not) – István Zachar Mar 19 '16 at 15:45
• Related: (110490). – user31159 Mar 19 '16 at 15:45
• Duplicate question on Stack Overflow: (6267143) – Mr.Wizard Mar 19 '16 at 20:28

There is a nice analysis about the issue at hand at Stack Overflow 6267143 (the finding of Mr.Wizard). I'd redirect anyone to read the answers there for those are very enlightening. Here I only provide a recap of the essentials as a community wiki.

"Unevaluated gets stripped when it occurs as the outermost wrapper in a rule. This is how Unevaluated works, i.e. Unevaluated is not a regular symbol which evaluates anything." (Sasha)

Unevaluated is an inert symbol without values or definitions, only attributes HoldAllComplete and Protected. Therefore, nothing is performed on it when appears naked, e.g. Unevaluated[1+1] remains as it is. However, it's behaviour is not typical to those of inert heads. When the evaluator, traversing an expression tree from top to leafs, meets with Unevaluated, it strips it. Then, when passing up the tree at the end of evaluation, "the Unevaluated wrappers are restored for those parts of expression where no applicable rules were found" (Leonid) (e.g. undefined functions like f). This basically means the followings:

1. If Unevaluated appears within a function that does nothing, it is ultimately retained, as no evaluation is done at all:

In[1]:= ClearAll[f]; f[Unevaluated[1 + 1]]

Out[1]= f[Unevaluated[1 + 1]]

2. If Unevaluated appears within a function that has rules defined for processing its arguments, it gets stripped as the evaluator digs in. Note however, that the number of Unevaluateds being stripped depends on the number of evaluations that subexpression will participate in (an answer to the comment of xzczd above). (This is not a number that can trivially be calculated beforehand.)

In[2]:= 1 -> Unevaluated[1 + 1]

Out[2]= 1 -> 2

or an even simpler example:

In[3]:= Identity[Unevaluated[1 + 1]]

Out[3]= 2

3. If Unevaluated appears within a function that holds its arguments, it gets stripped but the arguments are still held per the attribute(s) of the function (RuleDelayed in this example):

In[4]:= 1 :> Unevaluated[1 + 1]

Out[4]= 1 :> 1 + 1

Summary

This is no bug, and not some specific behaviour of Rule or RuleDelayed. Due to the generally unknown number of evaluations a subexpression goes through during evaluation, Unevaluated should not be used as a general method to shield evaluation -- use Hold for that. And read the full analysis here.

• "This is …… not some specific behaviour of Rule or RuleDelayed." I'm sorry but I don't agree. If so, can you build a function that has the same behavior? (Typically the last example in my question. ) – xzczd Nov 5 '18 at 8:36
• @xzczd Yes and no. In one hand, RuleDelayed is specific, but on the other hand, you can simulate it's evaluation sequence, as Leonid did in his answer here. BTW, feel free to edit in, as this is a CW. – István Zachar Nov 5 '18 at 8:47