6
$\begingroup$

While trying to create a pretty version of Trace, I stumbled across a weird issue which I finally tracked down to the following:

Annotation[0, Hold@Slot@i]

This expression throws an error:

Function::slot: Slot[i] (in #1&) should contain a non-negative integer or string.

After even more digging, I found the definition that causes the issue:

WrappersDump`WrapperToBoxes[Annotation[expr_], data_, fmt_] := 
  TagBox[MakeBoxes[expr, fmt], Annotation[#, data] &];

Obviously, the Function around Annotation sees the invalid Slot and starts to complain.

The question is now: How can I avoid this?

To give a bit more context: The issue is arising inside my Trace postprocessor, that, among other things, creates Tooltips for all expression in the trace (similar to Tooltip[Short[#],#]&). Since Trace wraps everything in HoldForm, nearly everything works perfectly, until there is an invalid Slot somewhere (which originates from a piece of code that builds dynamically builds a Function), thanks to Tooltip using Annotation.

Update 2 - A more reasonable example

Since the above example admittedly seems extremely contrived, I'll try to provide a better one:

Consider the following function to shorten the output of Trace:

shortenTrace[l_List] := shortenTrace /@ l
shortenTrace[i_] := Tooltip[Short[i, 0.1], i]

This simply Shortens every item in the trace and places the long expression in the tooltip. This works great:

Table[Print@i,{i,3}]//Trace//shortenTrace
(* 1 *)
(* 2 *)
(* 3 *)
(* {<<1>>,{{i,1},Print<<1>><<1>>],{<<9>>[<<1>>],1},Null},{{i,2},Print<<1>><<1>>],{<<9>>[<<1>>],2},Null},{{i,3},Print<<1>><<1>>],{<<9>>[<<1>>],3},Null},{<<1>>}} *)

Now consider this example:

Table[With[{s = Slot@i}, s &], {i, 3}]
(* {#1 &, #2 &, #3 &} *)

Let's trace it:

Table[With[{s=Slot@i},s&],{i,3}]//Trace
(* {Table[With[{s=Slot[i]},s&],{i,3}],{With[{s=Slot[i]},s&],{{i,1},#1},#1&},{With[{s=Slot[i]},s&],{{i,2},#2},#2&},{With[{s=Slot[i]},s&],{{i,3},#3},#3&},{#1&,#2&,#3&}} *)

Table[With[{s=Slot@i},s&],{i,3}]//Trace//shortenTrace
Function::slot: Slot[i] (in #1&) should contain a non-negative integer or string.
Function::slot: Slot[i] (in #1&) should contain a non-negative integer or string.
Function::slot: Slot[i] (in #1&) should contain a non-negative integer or string.
General::stop: Further output of Function::slot will be suppressed during this calculation.
(* {<<1>>,{<<1>>,{{i,1},#1},#1&},{<<1>>,{{i,2},#2},#2&},{<<1>>,{{i,3},#3},#3&},{#1&,#2&,#3&}} *)

As you can see, the trace output contains Slot[i], which causes the problem in shortenTraceTooltipAnnotation.

Update

I thought about it some more and found a solution (see answer below).

Also: Should this be considered a bug? It seems to me that a function like Tooltip that's purely for display purposes should break when trying to display semantically incorrect expressions.

$\endgroup$
  • $\begingroup$ So Annotation[0, Hold@Slot@i] causes the problem, but what is a minimal example that makes sense and produces the problem. This piece is invalid so it throws an error, can you show a valid piece of code which behaves incorrectly? (sorry if I missed something) $\endgroup$ – Kuba Sep 10 '17 at 9:37
  • $\begingroup$ @Kuba I added an example that is closer to my real-world issue $\endgroup$ – Lukas Lang Sep 10 '17 at 9:57
3
$\begingroup$

Another approach is to patch the internal function itself, e.g.

WrappersDump`WrapperToBoxes[Annotation[expr_, data_], fmt_] := 
  TagBox[
    MakeBoxes[expr, fmt], 
    Function[BoxForm`x, Annotation[BoxForm`x, data]]
  ]
  • I cannot promise this will not introduce bugs of its own so in practice I would use Kuba's method, specifically the ToString variant.

  • Don't miss the three-parameter Annotation rule if you pursue this, for e.g. Annotation[0, Hold@Slot@x, "Tooltip"]

$\endgroup$
3
$\begingroup$

I don't know how to judge it, will think about that, meanwhile since Tooltip is meant for display anyway we can speed up formatting so that #i won't interfere with Annotiation's definitions:

shortenTrace // ClearAll
shortenTrace[l_List] := shortenTrace /@ l
shortenTrace[i_HoldForm] := Tooltip[
  Short[i, 0.1]
, ToString[#, InputForm]&[Unevaluated @@ i]
]

At the beginning I had MakeBoxes[i] but, as noticed by OP, it won't help with e.g. Dynamic[#] as it typesetts to DynamicBox[ToBoxes[#1, StandardForm]] so # can still interfere

$\endgroup$
  • $\begingroup$ This seems a lot more straightforward than my approach, thanks - however, it seems to have issues when the tooltip contains Dynamic expressions... (e.g. simply Dynamic@i instead of i). I'll think about whether this is an issue of this solution or simply a design flaw on my part $\endgroup$ – Lukas Lang Sep 10 '17 at 13:42
  • $\begingroup$ Thanks for the update - unfortunately, in my case, ToString won't work either (although it works a lot better) for all inputs, as some of the tooltips are built containing Dynamic, so that ToString breaks them. It seems that any method that tries to transform the tooltip content is doomed if there is a Dynamic in there that needs to stay $\endgroup$ – Lukas Lang Sep 12 '17 at 8:54
  • $\begingroup$ One example: Let's try to fix Tooltip[Short[i, 0.1], Short[i, 0.3]], i.e we also limit the length of the tooltip. If I wrap the ToString around, tooltip reads Short[...], as Short is only interpreted for display. If I put it inside, the tooltip is shortened differently, as it's just a string now (e.g. {0,… , 0} instead of {0,<<8>>,0} for Short [Table[0, 10], 0.1]) $\endgroup$ – Lukas Lang Sep 12 '17 at 9:42
  • $\begingroup$ How so? (What I wanted to show with the example is that, depending on what the tooltip should show, wrapping it in ToString to hide Slot@i breaks other things, e.g. a Short wrapper) $\endgroup$ – Lukas Lang Sep 12 '17 at 10:54
  • $\begingroup$ @Mathe172 if you don't want strings then I'd go with your solution. $\endgroup$ – Kuba Sep 13 '17 at 7:27
2
$\begingroup$

After thinking about it some more, I found an answer:

Annotation[0,Slot@i/.s:(Slot|SlotSequence):>Annotation[s,"Inactive"]]

or

Tooltip[0,Slot@i/.s:(Slot|SlotSequence):>Annotation[s,"Inactive"]]

This simply uses Annotation (kind of ironic) to hide Slot from Function. Due to the nature of Annotation, this is invisible when displayed. It is also straightforward to uniquely identify the "protected" Slots and unprotect them. One could even use a Unique symbol in place of "Inactive" to go a step further.

$\endgroup$
  • 1
    $\begingroup$ Consider possibly s : (Slot | SlotSequence) :> Defer[s] as a shorter alternative. $\endgroup$ – Mr.Wizard Sep 11 '17 at 22:01

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.