# How does MakeBoxes handle an n-ary operator?

I want to format results from Reduce using the $\cup$ symbol instead of the $\lor$ symbol. For example, Reduce[x^2 > 4, x, Reals] produces $x<-2\lor x>2$ while I would like it to produce $x<-2\cup x>2$.

My current solution is to add the head xO to use in place of the Or head and associate the appropriate formatting with the xO head using MakeBoxes.

xO /: MakeBoxes[xO[x_, y_], form_] :=
RowBox[{MakeBoxes[x, form], "\[Union]", MakeBoxes[y, form]}];


When I want to invoke the special formatting, I simply substitute xO for Or.

Reduce[x^2 > 4, x, Reals] /. Or -> xO


This produces the desired result of $(x<-2)\cup (x>2)$. I am feeling like a master bit-twiddler at this point.

Of course, my next test case didn't work.

Reduce[x^3 + 2 x^2 - x - 2 == 0] /. Or -> xO
(* xO(x==-2,x==-1,x==1) *)


My MakeBoxes[xO[x_, y_]] definition above does not match the case when xO has three arguments. I added the following to limp along a little further.

xO /: MakeBoxes[xO[x_, y_, z_], form_] :=
RowBox[{MakeBoxes[x, form], "\[Union]", MakeBoxes[y, form], "\[Union]", MakeBoxes[z, form]}];


This works for three arguments, but is clearly not a robust solution. What is the general method for an n-ary function?

I tried several variations of the following:

xO /: MakeBoxes[xO[x_, y__], form_] :=
RowBox[{MakeBoxes[x, form], "\[Union]", MakeBoxes[y, form]}];


which produced the error:

MakeBoxes::argt: "MakeBoxes called with 3 arguments; 1 or 2 arguments are expected"


when applied to the Reduce[x^3 + 2 x^2 - x - 2 == 0] /. Or -> xO test case.

I am probably missing something obvious. Any ideas?

You were almost there. You just need to use the multiple-argument pattern, and generalize your code accordingly to create the internals of RowBox programmatically:

xO /: MakeBoxes[xO[x___], form_] :=
RowBox[
Riffle[
Map[MakeBoxes[#, form] &, {x}],
"\[Union]"
]
]


Note however that the above implementation leaks evaluation. It may or may not be a problem, but for example here:

x = 1;
xO[x == 1, x > 1, x < 1]


one may argue that the desired result should not be sensitive to the possible global values that x may have, so the result:

(* True \[Union] False \[Union] False *)


may be unsatisfacory. Thus, here is a more careful version:

ClearAll[xO];
SetAttributes[xO, HoldAllComplete];
xO /: MakeBoxes[xO[x___], form_] :=
RowBox@Riffle[
List @@ Replace[
HoldComplete[x],
elem_ :> With[{eval = MakeBoxes[elem, form]}, eval /; True],
{1}
],
"\[Union]"
]


which now gives

xO[x == 1, x > 1, x < 1]

(* x == 1 \[Union] x > 1 \[Union] x < 1 *)

• Works perfectly, but it would have taken me an long and indeterminate amount of time to come to that solution. Thank you @Leonid. Mar 5, 2013 at 0:09
• @RandomBits Glad I could help. See also my update on evaluation leaks and the second version. Thanks for the accept. B.t.w., typically it is better to wait for a while before accepting an answer, to encourage others to contribute more answers. You can also accept a different answer at any time later. Mar 5, 2013 at 0:10
• I understand the use of SetAttributes[xO, HoldAllComplete], but I am having trouble understanding the details of the rule inside of Replace. It looks like the gist of it is that you are safely evaluating MakeBox for each argument of xO. Mar 5, 2013 at 2:32
• @RandomBits I should have given more explanations. I need to pass each of the arguments of the x sequence to MakeBoxes. I use HoldComplete to avoid their premature evaluation. To evaluate them inside HoldComplete, I use Trott-Strzebonski technique. This is actually an overkill here, I could have just used elem_ :> MakeBoxes[elem, form], and then all these would evaluate at the point when HoldComplete head is changed into List. Mar 5, 2013 at 2:38
• @RandomBits In other words, List @@ Replace[ HoldComplete[x], elem_ :> MakeBoxes[elem, form], {1}] should work just as well here, while being less magical. Mar 5, 2013 at 2:40

You could make use of the internal typesetting function BoxFormMakeInfixForm for this purpose:

xO /: MakeBoxes[xO[a__], form_] := RowBox[BoxFormMakeInfixForm[Or[a], "\[Union]", form]]


The key feature is that parenthesization of the arguments is controlled by the precedence of the head of the first argument, in this case Or. Then:

xO[x == 1, x > 1, x < 1]

x == 1 \[Union] x > 1 \[Union] x < 1


If you want parenthesization of the arguments to be based on the precedence of Times instead of Or, you would do:

xO /: MakeBoxes[xO[a__], form_] := RowBox[BoxFormMakeInfixForm[Times[a], "\[Union]", form]]

xO[x == 1, x > 1, x < 1]

(x == 1) \[Union] (x > 1) \[Union] (x < 1)
`