Post-answer note: this question is (understandably) marked as a duplicate, and indeed has a similar solution, but is subtly different in focus: the linked question is about returning a function call unevaluated, but this is about being able to produce side-effects while doing so, like error messages. The relevant point in @Sjoerd Smit's answer to this question (which isn't so explicit in the other) is that the test in any
Condition
(/;
), even one with an unreachable result, is always evaluated (as part of trying that definition) and so can be used for these side effects—which is pretty nifty!
Consider the (bad) input NSolve[1,1,1]
. This yields an error message, and returns NSolve[1,1,1]
. This return value seems to be literally the expression NSolve[1,1,1]
: NSolve[1,1,1] // FullForm
is again NSolve[1,1,1]
, and it seems usable as an inert expression internally (e.g. Nsolve[1,1,1] /. {1 -> 3}
sends two error messages, one each time it attempts evaluation, and ultimately returns NSolve[3,3,3]
).
I want to make a function that behaves like this, and I'm curious how Mathematica manages to do this without hitting the recursion limit. Consider a function which throws an error if its argument is 0
and otherwise returns 1
.
f::err = "Error!";
f[a_] := If[a == 0, Message[f::err]; f[a], 1]
f[0]
obviously goes into an infinite loop of definition application. The attempts
f[a_] := If[a == 0, Message[f::err]; Unevaluated[f[a]], 1]
f[a_] := If[a == 0, Message[f::err]; Return[Unevaluated[f[a]]], 1]
f[a_] := If[a == 0, Message[f::err]; Defer[f[a]], 1]
f[a_] := If[a == 0, Message[f::err]; HoldForm[f[a]], 1]
are all inequivalent to whatever Mathematica does (as one can see upon applying FullForm
if necessary).
So what's Mathematica doing, or what's an equivalent implementation? (Am I missing something obvious?) Does Mathematica somehow retain a record of whether it has already tried to evaluate a certain expression during a given evaluation process somehow? Or is there a better "don't evaluate further" head than Unevaluated
or Defer
out there somewhere?
f[1] := f[1]; g[1] := g[1] + 0;
and what happens when we executef[1]
andg[1]
? (B) What happens inBlock[{x}, NSolve[x, 1, 1]]
andBlock[{}, NSolve[x, 1, 1]]
? (C) What's the difference betweenNSolve[1, 1, 1] /. 1 -> 1
andNSolve[1, 1, 1] /. 1 -> 3
? $\endgroup$NSolve[1, 1, 1] /. 1 -> 3
! $\endgroup$TracePrint
, it seems that the application off
's definition brings us back to the "top level" at which mathematica can tell that the expression is no longer changing, but the evaluation ofg[1]
ing[1] + 0
occurs within the evaluation ofPlus
, and so is not compared to the originalg[1]
. $\endgroup$