31
$\begingroup$

I have some code that looks like

Table[
    a = 1;
    b = {2, 3}
    c = i;,
    {i, 2}
]

which gives an error:

Set::write: Tag Times in c {2,3} is Protected. >>

In this case, it is quite clear that there is a semicolon missing after b = {2, 3}, which is causing this error. However, sometimes I encounter this in large code blocks spanning several lines, which is very difficult to debug.

How can I automate this semicolon hunting to make debugging easier?

$\endgroup$
3
  • 29
    $\begingroup$ Worth to mention that Tag Times ... is Protected message is in 90% cases the result of a missing semicolon. $\endgroup$
    – Kuba
    Feb 3, 2016 at 7:02
  • 3
    $\begingroup$ Please, upvote the comment of Kuba! $\endgroup$
    – Rexcirus
    Mar 1, 2016 at 20:36
  • 1
    $\begingroup$ @Rexcirus I laughed out loud at that (A+) $\endgroup$
    – b3m2a1
    Jul 8, 2018 at 4:07

5 Answers 5

37
$\begingroup$

Here is a function findBadSets that will find any explicitly bad Set/SetDelayed attempts in a given expression. Simply wrap it around a syntactically complete block of code, or follow the block with // findBadSets and the errors are printed one per row, protected symbol followed by complete left-hand side for each bad Set:

(* your example *) // findBadSets

Code for the function:

SetAttributes[findBadSets, HoldFirst]

findBadSets[expr_] :=
  Cases[
    Unevaluated @ expr,
    (Set | SetDelayed)[bad : head_Symbol[___], _]
      /; MemberQ[Attributes@head, Protected] :>
        HoldForm[Row[{head, bad}, Spacer[50]]],
   -1] // Column

See also:

$\endgroup$
7
  • 1
    $\begingroup$ WoW, so useful! $\endgroup$
    – matheorem
    Nov 15, 2013 at 1:32
  • 1
    $\begingroup$ @matheorem My pleasure. :-) $\endgroup$
    – Mr.Wizard
    Nov 15, 2013 at 19:03
  • 1
    $\begingroup$ That's certainly a faq. $\endgroup$
    – Kuba
    Feb 3, 2016 at 6:44
  • 1
    $\begingroup$ @Kuba thanks for tagging. $\endgroup$
    – Mr.Wizard
    Feb 3, 2016 at 6:54
  • $\begingroup$ This is a perfect example of what we could get from code helping functions. For some time now that WRI talks about the eventual inclusion of code improvement suggestion tools. A typical example given is the suggestion of WL functions that could do exactly what the user is programming with a more complex code. But even simpler things can be included, like the small code of Mr. Wizard, but also, for instance, a warning check of "all" the things listed on our common pitfalls post. Is there a frontend framework missing for this purpose? I would even be willing to participate/$ on a crowdfunding... $\endgroup$
    – P. Fonseca
    Feb 3, 2016 at 8:46
14
$\begingroup$

One can use Trace for this. I stuck in an extra, different error to show it is omitted in the output. It should also be clear how to trace other error messages.

foo = Trace[
  Table[
    a = 1/0;
    b = {2, 3} 
    c = i;,
   {i, 2}],
  HoldPattern@Message[Set::write, ___],
  TraceAbove -> True]
(* Power::infty, Set::write errors *)

Mathematica graphics

If you would like a function like Mr.Wizard's to apply to a code block, we can wrap it up like this:

traceSet = Function[code,
  Trace[
   code,
   HoldPattern@Message[Set::write, __],
   TraceAbove -> True],
  HoldAll]

Here is a way to view the list of lists of lists of output, somewhat reminiscent of WReach's traceView. (His function is built on TraceScan, which I could not get to work in the way above. The option TraceAbove -> True seems to be ignored. Is that a bug?)

Clear[ov];
ov[{e1_HoldForm, rest___}, open_: False] := 
  OpenerView[{e1, Column[ov[#, open] & /@ {rest}, Dividers -> All]}, open];
ov[e_, _] := e

Example:

ov[foo, True]

Mathematica graphics

Now you can see the offending code comes from

a = 1/0; b = {2, 3} c = i;

which leads to the actual error, {2, 3} c = i. Note that the error message already informs us that the immediate source is c {2,3}.

If it's a very large chunk of code, you can select the bottom segment of the evaluation chain as follows. The length of the chain is controlled by

Length@First@pos == 5

which can be adjusted to suit:

Cases[foo, 
  e_List /; 
   With[{pos = Position[e, HoldPattern@Message[Set::write, ___]]},
    Length[pos] == 1 && Length@First@pos == 5], Infinity] // 
 Column[#, Dividers -> All] &

Mathematica graphics

One could also apply the OpenerView to each element in the output by adding to the pattern in the second argument of Cases the code :> ov[e, True] to make it a transformation rule.

$\endgroup$
8
$\begingroup$

After reading this and this answer, I noticed that the debugger is also useful for identifying the error source in this case. You just need to:

  1. Go to Evaluation and check on Debugger.

  2. Check on Break at Messages and click the Show Stack button (alternatively you can click the button after running the erroneous code) in the newly opened panel.

  3. Run the erroneous code.

  4. Go to the bottom of Stack window (opened by the Show Stack button), click the last button, and the erroneous part of code will be highlighted:

enter image description here

  1. Click on Abort in the panel (in some cases you may need to click for several times) to stop the evaluation, and correct the discovered error.
$\endgroup$
6
$\begingroup$

A couple other ideas. One is to print the stack when a message occurs:

TracePrint[
    Table[
        a = 1;
        b = {2, 3}
        c = i;,
        {i, 2}
    ],
    _Message,
    TraceAction->(Print[Stack[_][[-3]]]&)
]

{2,3} c=1

Set::write: Tag Times in c {2,3} is Protected.

{2,3} c=2

Set::write: Tag Times in c {2,3} is Protected.

{Null, Null}

Another is to just click on the ... to the left of the message, and then click on "Show Stack Trace". You will see the problematic Set expression in the stack trace.

$\endgroup$
3
  • $\begingroup$ A "meta" question here: How does a problem like this get 14,000 views when adjacent questions get roughly 30 views? $\endgroup$ Jul 10, 2017 at 22:18
  • 2
    $\begingroup$ @DavidG.Stork it is 5y old, probably was on Hot across SE, it is high in results for Set::write: Tag Times is Protected query. And we link other questions here. $\endgroup$
    – Kuba
    Jul 11, 2017 at 6:46
  • $\begingroup$ A great answer. $\endgroup$ May 9, 2020 at 3:13
0
$\begingroup$

Sometimes it can also be caused by wrongly constructing functions with subscripts, and the "findBadSets" solution above may not return anything useful at all. And also Clear[ ] wont help and one may need to restart the kernel.

P.D. Restarting the kernel usually solves 10% of my bad code problems.

I.e.: Lets imagine we wanted to construct a function $f_n(x)=x+1$. Our first attempt:

enter image description here

So, restarting the kernel (Evaluation menu -> Quit Kernel -> Local) and trying the proper way:

enter image description here

$\endgroup$
1
  • 1
    $\begingroup$ The trouble with Subscript[] is discussed here (basic advice: don't use it this way; a better practice would be f /: Subscript[f, n] = x + 1). The problem with findBadSets seems to be with bad sets of subvalues and not with Subscript[] per se. findBadSets seems to work with bad sets of downvalues and ownvalues. Carl's TracePrint[] method handles all of them, as well as answers the question of how to find the source of the error(s). $\endgroup$
    – Michael E2
    Jan 7 at 22:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.