20

Assuming each odd line is input I would like the even lines to be the output of the following lines.

enter image description here

In the above the Graphics are inside Hold/HoldForm therefore IMO it doesn't make sense to have Mathematica try and render such expressions. For example Hold[Graphics[{Red, Circle[]}]] throws an error because built in colors don't get replaced.

How might I prevent Graphics from being rendered and instead print the Graphics code?

The following code works at reassigning Graphics inside Hold/HoldForm, but it prints something like Graphics[{Circle[{0, 0}]}] without the HoldForm/Hold.

Unprotect[Graphics]
Graphics /: HoldForm[Graphics[x___]] := (
   InputForm[Graphics[x]]
   );
Graphics /: Hold[Graphics[x___]] := (
   InputForm[Graphics[x]]
   );

Ideally the code should be generalized to work with irregular constructs like HoldForm["a",Graphics[{Circle[{0, 0}]}]]. More importantly, how might you insure HoldComplete doesn't render Graphics either. As rm-rf pointed out the Villegas–Gayley trick will likely be needed.


Simplifying John Fultz's answer slightly, you get the following:

Map[
 (
   Unprotect[#];
   # /: MakeBoxes[#[expr_], fmt : StandardForm | TraditionalForm] := 
    Block[{Graphics, Graphics3D},
     RowBox[{ToString[#], "[", MakeBoxes[expr, fmt], "]"}]
     ];
   Protect[#]
   ) &, {Hold, HoldForm, HoldComplete}
 ]
Carl Woll
  • 130,679
  • 6
  • 243
  • 355
William
  • 7,595
  • 2
  • 22
  • 70

5 Answers5

18

Recall that the rendering of Graphics has nothing to do with evaluation. It is done entirely in typesetting. And therefore, a robust solution will treat this as a problem of typesetting, and not as a problem of evaluation.

Once you frame the problem properly, the solution is fairly straightforward. What you want to do is to change the typesetting of Hold (and friends). Take a look at this:

Unprotect[Hold];
Hold /: MakeBoxes[Hold[expr_], fmt : StandardForm | TraditionalForm] :=
  Block[{Graphics, Graphics3D}, Unprotect[Graphics, Graphics3D]; 
  Clear[Graphics, Graphics3D]; 
  RowBox[{"Hold", "[", MakeBoxes[expr, fmt], "]"}]]
Protect[Hold]

Fortunately, Hold (and HoldForm and HoldComplete) has no typesetting rules directly attached to it that you're fighting, which you can determine using FormatValues[Hold]. But Graphics and Graphics3D do; it's how typesetting of graphics works at all. We want to suppress those rules, but only within the typesetting of Hold. So we use Block to contain the damage we're about to do to the Graphics and Graphics3D symbols, and then use Clear to clear them. From there on out, we let MakeBoxes do what it would normally do.

Note that this example cheats a bit; it only works if you pass one argument to Hold. I did that for purpose of code simplicity and illustration. To make the formatting rules work properly for Hold[expr___], I would have to write multiple and more sophisticated rules, or I would have to use the Villegas-Gayley trick.


Edit: As came up in the comment discussion, it really isn't necessary to Unprotect and Clear the symbols Graphics and Graphics3D, as Block is effectively doing that already. I've considered editing the code to make it shorter/simpler, but perhaps the existing code is clearer for people who don't fully understand how Block works (and, public confession here, while I understand Block scoping, I had just plumb forgotten how Block initializes variables, so this more an oversight on my part than a planned teaching moment).

John Fultz
  • 12,581
  • 58
  • 73
  • 3
    Nice explanation and solution,+1. It is not clear to me however, why Unprotect and Clear are needed, since Block automatically clears all symbol's definitions and attributes (in contrast to Internal`InheritedBlock). – Leonid Shifrin Sep 14 '13 at 13:34
  • 2
    @LeonidShifrin I think it was simply John Fultz kind attempt to make it clearer to what is exactly going on. They way it is currently written makes perfect sense(at least to me). Additionally if I don't understand the differences between With,Module, and Block/Internal\InheritedBlock`. How do you prevent evaluation of a function? Well by Removing and Clearing it of course. – William Sep 14 '13 at 14:55
  • 3
    Actually, believe it or not, there are limits to my knowledge of Mathematica, and for some reason, it didn't occur to me to see if Block would automatically clear a Protected System symbol. I appreciate @Liam reading the benefit of hidden wisdom into my answer, though! :) And, in fact, that response gives me pause about editing the answer to shorten the code. – John Fultz Sep 14 '13 at 16:36
  • Also Clear probably only clears OwnValues and DownValues etc. The following code shows you cannot use Clear to get rid of the behavior of Graph, but you can use Block. ue = UndirectedEdge; gr:= Graph[{ue[1,2],ue[2,3]}]; AtomQ/@{gr, Unprotect@Graph; Clear@Graph; gr, Unevaluated@@Block[{Graph},Hold@Evaluate@ gr ] } evaluates to {True, True, False} – Jacob Akkerboom Jan 01 '14 at 21:07
  • ...where one should realize that AtomQ[Unevaluated[1]] gives True. – Jacob Akkerboom Jan 01 '14 at 21:08
  • @JacobAkkerboom Note that if one changes gr := Graph[{ue[1, 2], ue[2, 3]}]; into gr = Graph[{ue[1, 2], ue[2, 3]}]; (that is SetDelayed to Set) in your code it gives {True,True,True} instead of {True, True, False}. What is the reason for this difference? – Alexey Popkov Jan 02 '14 at 18:34
  • @JacobAkkerboom AtomQ has no any Hold* attribute, so AtomQ[Unevaluated[1]] is identical to AtomQ[1]. Note that AtomQ[Unevaluated[1 + 1]] returns False. – Alexey Popkov Jan 02 '14 at 18:39
  • 1
    In reply to your last comment: Yes, you are correct. I was hoping to clear any confusion when I was comparing AtomQ[Graph[...]] with AtomQ[Unevaluated[Graph[...]]], where the former gives True and the latter gives False. Unevaluated is not responsible for the difference :) – Jacob Akkerboom Jan 02 '14 at 18:43
  • 1
    @AlexeyPopkov in reply to your first comment: ah yes this is precisely the point :). If you "Set" gr = Graph[...], then the expression with head Graph is evaluated outside the Block. Note that blocking Graph later will have no effect as the expression with head Graph is already converted into an atomic expression. Even if it's Head is still Graph, it is not a "proper head". Anyway the function Graph generates this atomic expression. If you evaluate gr inside the Block, then the expression with head Graph is not converted into an atom, as Graph has no special meaning. – Jacob Akkerboom Jan 02 '14 at 18:48
  • @JacobAkkerboom Thanks for the explanations, it is clear now. And I think it is worth to post in a separate answer your (very significant!) points. – Alexey Popkov Jan 02 '14 at 18:57
11

John Fultz alluded to using the Villegas-Gayley pattern. Since I believe that is the correct approach to this problem here is an implementation.

mk : MakeBoxes[(Hold | HoldForm | HoldComplete | HoldPattern)[__], _] :=
  Block[{$hldGfx = True, Graphics, Graphics3D}, mk] /; ! TrueQ[$hldGfx]

I included HoldPattern to complete the Hold functions. This now works at any depth:

Hold[1, 2, foo[3, Graphics[{Green, Circle[]}], 4], 5]
Hold[1, 2, foo[3, Graphics[{Green, Circle[]}], 4], 5]

Overhead

Jacob Akkerboom questioned the overhead of this general rule attached to MakeBoxes. To test this I converted a large expression to Box form using ToBoxes (which calls MakeBoxes), and timed the operation with and without this definition as well as several alternatives. Here are the results (in version 7). Each test was performed in a fresh kernel, using this code:

expr = Expand[(1 + x + y)^4 (2 - z)^5 (q - 7 - a)^7 (b + r - 4)^6];
ToBoxes[expr] // AbsoluteTiming // First
  • Raw (no additional MakeBoxes rules): 0.7940454
  • Alternatives DownValue on MakeBoxes: 1.1030631
  • Four individual DownValues on MakeBoxes: 1.4690841
  • Four UpValues on Hold* functions: 0.7890451

(Incidentally, use of the Notations Package causes far larger overhead; the timing performed in my standard configuration was 3.5652039 seconds.)

It appears that Jacob's concern is valid as the overhead of my method above is significant, though not extreme. I usually attach rules to MakeBoxes to avoid having to Unprotect system Symbols but I may have to reconsider that practice. If you prefer unprotecting system Symbols to the overhead you may use this:

(
  Unprotect @ #;
  mk : MakeBoxes[Blank[#], _] /; ! TrueQ[$hldGfx] ^:= 
    Block[{$hldGfx = True, Graphics, Graphics3D}, mk];
  Protect @ #
) & ~Scan~ {Hold, HoldForm, HoldComplete, HoldPattern}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Thank you! Now there are 2 good answers. I don't love the idea of changing the right answer after so long, but this is definitely the right/better/more complete answer. – William Jan 01 '14 at 22:55
  • @Liam You're welcome, and thanks for the Accept. – Mr.Wizard Jan 02 '14 at 02:50
  • @Liam I simplified my code; please take a look and let me know if you run into any problems. – Mr.Wizard Jan 02 '14 at 06:21
  • @Mr.Wizard I suppose that this definition will slow down MakeBoxes quite a bit. When you call MakeBoxes[whatever[b]], I imagine it will at least have to test something like Hold === whatever, HoldForm === whatever etc. I think the definition will be associated with MakeBoxes. Also I suspect MakeBoxes is used once whenever you type a character in the front end. What are your thoughts on this? – Jacob Akkerboom Jan 02 '14 at 10:15
  • @Jacob please see update – Mr.Wizard Jan 02 '14 at 17:51
  • @Mr.Wizard very interesting, thank you kindly! :) – Jacob Akkerboom Jan 02 '14 at 18:34
  • You forgot some alternatives :P Hehe. Btw, I also like this approach more, +1 – Rojo Jan 02 '14 at 18:53
  • @Rojo which one did you want me to include? John's answer also uses UpValues so I assumed it would perform the same as my second method. Did I miss something else? – Mr.Wizard Jan 02 '14 at 19:50
  • @Mr.Wizard, chat? – Rojo Jan 02 '14 at 19:53
  • @Rojo Sorry, no time now. Later I hope! :-) – Mr.Wizard Jan 02 '14 at 20:02
4

This is not an answer but an extended comment.

Regarding your assertion

This occurs because although the front end attempts to render the Graphics element the internal code won't replace Directives inside of a Held expression.

This is not the case. Consider

Hold[Graphics[{RGBColor[1, 0, 0], Thick, Circle[]}]]

thickred.png

and

 With[{red = Red}, Hold[Graphics[{red, Circle[]}]]]

withred.png

So the error message comes from using the built-in symbol Red and not from the front end doing anything funny with directives.

m_goldberg
  • 107,779
  • 16
  • 103
  • 257
  • Is Color Directives more appropriate then? Seems to be an issue for all colors. – William Sep 14 '13 at 01:59
  • @Liam. RGBColor[1, 0, 0] is a color directive; Red,Green, Blue, etc. are not. – m_goldberg Sep 14 '13 at 02:02
  • 1
    That's fair although I find it odd that the colors are listed under guide/GraphicsDirectives Graphics Directives in the docs. Would simply colors be an appropriate name? – William Sep 14 '13 at 02:04
  • 1
    @Liam. I regard that to be an error in the docs. They are symbols that evaluate to graphic directives, but of course not inside Hold. – m_goldberg Sep 14 '13 at 02:08
  • @Liam. You might well ask "Why then does Thick work?" I wonder about that myself. – m_goldberg Sep 14 '13 at 02:12
  • @m_goldberg I should think the answer is obvious. Thick is a graphics directive. The documentation for Thick directly makes this statement. As for Red, Green, etc., you're absolutely correct in saying they aren't graphics directives. But they do evaluate directly into a graphics directives. For that reason, I don't think the choice to put them in the guide/GraphicsDirectives guide page is inappropriate. – John Fultz Sep 14 '13 at 08:15
3

In M11+, if you are willing to create a new Hold* wrapper, you could define:

SetAttributes[HoldFormatting,HoldAll];

MakeBoxes[HoldFormatting[expr_], StandardForm]^:=MakeBoxes[DisableFormatting[expr]]

Then:

HoldFormatting[{1+1, Graphics[{Green,Circle[]}]}]

{1 + 1, Graphics[{Green, Circle[]}]}

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
2

If you take @JohnFultz 's initial comment

Recall that the rendering of Graphics has nothing to do with evaluation. It is done entirely in typesetting. And therefore, a robust solution will treat this as a problem of typesetting, and not as a problem of evaluation.

then I would ask, why are you using Hold in the first place? Hold is for the kernel, not for the front-end. I tried your Hold[Graphics[{Red, Circle[]}]] and saw the error too; but look at the expression inside the notebook (Cmd-Shift-E on Mac)

Cell[BoxData[
 RowBox[{"Hold", "[", 
  GraphicsBox[{Red, CircleBox[{0, 0}]}], "]"}]], "Output",
 CellChangeTimes->{3.597606336110736*^9}]

The front-end wraps all in a Cell (content in a notebook lives inside cells). Cell contains boxes, and boxes are generated from the expression. It also replaced Graphics by GraphicsBox and Circle by CircleBox. Red is unknown to the front-end as it is.

Try

Graphics[{Red, Circle[]}] // InputForm

and you see

Graphics[{RGBColor[1, 0, 0], Circle[{0, 0}]}]

Funny enough, it seems the kernel knows about Red and Circle's default and the front-end gets those symbols evaluated. Graphics gets passed as is, although -I bet- it must have some rules in the kernel that allow the front-end to get a clearer command.

If you do

Hold[Graphics[{Red, Circle[]}]] // InputForm

you see in the front-end

Hold[Graphics[{Red, Circle[]}]]

The kernel didn't attempt to resolve the expression at all - as expected.

I am far from being an expert on these things, but I think the central issue is trying to use Hold in the front-end, which as we can see, has no effect. The front-end has its own transformation rules, from the expressions it gets from the kernel to the cell and box objects it needs to display.

The good -and confusing- thing is that the kernel can manipulate those symbols too (Cell, boxes, InputForm, etc), so one can return formatted expressions to the front-end and this is how (IMHO) the problem should be approached.

So a very simple approach is just to use InputForm as suggested above.

carlosayam
  • 2,080
  • 16
  • 21