楼上xzczd说的挺好。
实际上只要理解Hold函数的作用那问题就很清楚了。Hold类属性不是说保持一个表达式不计算,而是计算顺序先后的问题。
一个函数涉及到2部分计算,一部分是输入参数是否需要计算(例如f[1+1,2+2]里的加法),第2部分才是这个函数本身定义的算法。那么这两部分的计算,谁先进行谁后进行?不同的计算顺序的计算结果是有区别的。
1.输入参数先计算,然后f再计算。有时候会造成错误意思,首先看看哪些函数具有HoldFirst属性。
Select[Names["System`*"], MemberQ[Attributes[#], HoldFirst] &]
(*{"AddTo", "AppendTo", "AssociateTo", "Catch", "ClearAttributes", \
"Context", "Control", "CreateScheduledTask", "Debug", "Decrement", \
"DialogReturn", "DisplayWithRef", "DivideBy", "Dynamic", \
"Inactivate", "Inactive", "Increment", "KeyDropFrom", "Message", \
"MessageName", "MessagePacket", "NCache", "Pattern", "PreDecrement", \
"PreIncrement", "PrependTo", "Reap", "RefBox", "Refresh", \
"RepeatedTiming", "RuleCondition", "RunScheduledTask", "Set", \
"SetAttributes", "Stack", "SubtractFrom", "TableViewBox", "TimesBy", \
"ToEntity", "Unset", "UpSet", "WaitUntil"}*)
其中AppendTo为什么必须要有HoldFirst属性,我想是很显然的,因为AppendTo的用法就是AppendTo[a,3],其中a={1,2}。如果先计算输入参数,那么AppendTo[a,3]然后就等于AppendTo[{1,2},3],然后就和a无关了,AppendTo内部就无法修改a了。
顺带提一句,And函数具有HoldAll属性,这是十分必要的,想想一下如果没有这个属性,那么And[1>2,4>3]首先被计算成And[False,True],最后得到False,但是实际上我们计算完1>2后就没有必要计算4>3了,所以正是因为And具有HoldAll属性,所以And[1>2,4>3]的Trace里是没有包含4>3的计算的。那么问题来了,And的使用方法仅仅是And[1>2,4>3],不接受And[{1 > 2, 4 > 3}],而有时候我们的判断条件很多,例如用Table生成1000个判断条件,如果用And@@Table[],Apply是不具有Holds属性的,会把每个判断条件都计算。怎么处理这种情况,那么就是另外一个问题了。
2.输入参数不计算,统统套入f后再计算。有时候会造成重复计算。例如
SetAttributes[f, HoldAll];
f[x_] := {x^2, x^3, x^4};
f[1 + 1] // Trace
1+1被计算了3次,因为f的定义里出现了3次x。那按照典型的函数式编程,函数套函数?
SetAttributes[getMaxAndMin, HoldAll];
getMaxAndMin[x_] := {Max[x], Min[x]};
getMaxAndMin[RandomReal[1, 2]] // Trace
然后就崩了。