# 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. – RandomBits Mar 5 '13 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. – Leonid Shifrin Mar 5 '13 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. – RandomBits Mar 5 '13 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. – Leonid Shifrin Mar 5 '13 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. – Leonid Shifrin Mar 5 '13 at 2:40