4
$\begingroup$

Consider the following toy example:

prepareBoxes[foo[expr_]] := 
  message["The argument was " <> ToString@expr];

MakeBoxes[foo[expr_], StandardForm] := ToBoxes@prepareBoxes@foo[expr]

Evaluating foo[2] then gives the expected result:

foo[2]
(* message["The argument was 2"] *)

But if I want to review the definition of foo with Information I get the following:

enter image description here

Notice how the custom MakeBoxes gets evaluated on the LHS of the definition of prepareBoxes. This is weird enough, and arguably not the wanted result, but it may still be fine.

The problem arises if the definition of prepareBoxes is slightly different:

prepareBoxes[foo[expr_Integer]] := 
  message["The argument was " <> ToString@expr];
MakeBoxes[foo[expr_], StandardForm] := ToBoxes@prepareBoxes@foo[expr]

where I just changed the definition of prepareBoxes to only trigger when the input is an Integer. Evaluating Information on prepareBoxes with this definition we get an infinite recursion, for the same reason we got the wrong evaluation in the case above:

enter image description here

Is this behaviour considerable as a feature? It can be quite annoying in many occasions.

Is it avoidable? If so, how?

$\endgroup$
2
$\begingroup$

Think about what ToBoxes@prepareBoxes[foo[expr]] does when prepareBoxes[foo[expr]] does not actually evaluate to anything (because you have a literal expr instead of, say, 2). It makes something like

RowBox[{"prepareBoxes", "[", ..., "]"}]

But what goes in place of the ...? Of course ToBoxes has to translate foo[expr], for which it has to invoke MakeBoxes[foo[expr]]. And there you have the recursion.

You get a recursion when you evaluate foo[expr] (with expr a symbol). More precisely, you get a recursion when the result of this evaluation is typeset, as MakeBoxes is invoked only then. Displaying the definition causes similar things (in this case foo[expr_Integer]) to be typeset, thus also cause a recursion.

The proper way to deal with this is to restrict the pattern in the MakeBoxes definition (not in prepareBoxes).

foo /: MakeBoxes[foo[expr_Integer], StandardForm] := ...

Don't forget to clear your existing definitions first.


This is a very common beginner mistake when creating MakeBoxes definitions. I fell into this trap multiple times.

$\endgroup$
  • 1
    $\begingroup$ I think it's better to use TagSetDelayed as well, so something like foo /: MakeBoxes[foo[expr_Integer], StandardForm] := .... $\endgroup$ – Carl Woll Apr 5 '17 at 19:54
  • $\begingroup$ @CarlWoll I thought that the definition is associated with foo anyway, but you are correct: it is associated with MakeBoxes. Is there any reason I should use foo as the tag other than that then ClearAll[foo] would clear this too? $\endgroup$ – Szabolcs Apr 5 '17 at 19:59
  • 3
    $\begingroup$ I haven't thought carefully about the issue of UpValues of foo vs DownValues of MakeBoxes, but a couple reasons that come to mind are speed and code portability/readability. Using UpValues means that MakeBoxes for non-foo expressions should be unaffected. Also, if you use DownValues, then things like ??foo, Definition[foo], Save[file, foo] and Language`ExtendedDefinition["foo"] will not have those rules. I would prefer to have ??foo and the others contain all foo rules. $\endgroup$ – Carl Woll Apr 5 '17 at 20:37

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.