2
$\begingroup$

Sorry for a noob question, but this is something that I wonder about each time I work with Mathematica.

Coming from standard programming languages (Python and Java), I expect my functions to fail loudly if I pass them a wrong set of arguments. However in Mathematica (and on the level of annoyances, this is second only to the insane undo), when Apply[Function, Arglist] is given a wrong shape of arguments, it does not fail but perform some obscure manipulation without giving out any warning or error. Resulting bugs are often hard to detect and take up time and effort in development to get rid of.

So my question is two-fold:

  1. What are the reasons for such behavior?
  2. What can be done in the function definition to cause failure instead of a silent bug?
$\endgroup$
4
  • 1
    $\begingroup$ While I voted to close as a dupe, one clarification here is that Mathematica is a symbolic environment, and there are (almost) no real functions. Most of what you think of as functions are actually global replacement rules. When no rule matches, the expression is simply returned back - which makes sense in a symbolic environment, and is not a bug or an error. I recommend reading here and here, and also relevant sections of books by David Wagner and Roman Maeder, for more. $\endgroup$ May 20, 2014 at 17:52
  • $\begingroup$ @LeonidShifrin: Thanks for the rapid answer. However it is not exactly what I was looking for. My problem is not that I supply Strings instead of Integers or leave the field void, but rater supplying to the function {{x_},{x'},{x''}} instead of {x_, y_}. $\endgroup$ May 20, 2014 at 18:18
  • $\begingroup$ No problem. Hope it helps. $\endgroup$ May 20, 2014 at 18:20
  • $\begingroup$ Please also see: (23345), (85821) $\endgroup$
    – Mr.Wizard
    Jul 8, 2014 at 8:25

1 Answer 1

6
$\begingroup$

You can make your code very defensive in Mathematica if you are willing put in the effort it takes. When writing code that I expect will get a lot of use over a long period, both by me and others, I take pains to make the exposed function reasonably fool-proof.

When I'm worried about being passed invalid arguments, I almost always take one of two routes.

  1. Give my function the HoldAll attribute and validate the arguments myself int the function body. This is the method of choice when I want complete control over the error messages issued. I am likely to explicitly return $Failure after issuing an error message.

  2. Require the given arguments to match stringent argument patterns. This let's me draw on Mathematica's powerful pattern matching language to protect my function from bad calls.

Here are examples drawn from a turtle graphics package I have written. They mainly use the second approach, but the first one also does some internal validation because it processes options that use string tags to identify the option chosen.

turtleGraphics::"badargs" = "`1` doesn't know what to do with `2`";
turtleGraphics::"badopt" = "`1` isn't a valid `2`";

Options[draw] = {"graphics" -> True, "reset" -> True};

draw[t_Symbol, opts : OptionsPattern[{draw, Graphics}]] :=
 Module[{clrF, postF},
   clrF =
     If[OptionValue["reset"],
       clearTrack,
       Identity,
       Message[turtleGraphics::"badopt", 
         Style[{"reset" -> OptionValue["reset"]}, "SBO"], "drawing option"];
       Return @ $Failed];
   Switch[postF = OptionValue["graphics"],
     True | Graphics, 
       Graphics[render[t, clrF], Evaluate[FilterRules[{opts}, Options[Graphics]]]],
     False | Identity, render[t, clrF],
     _Symbol | _Function, postF @ render[t, clrF],
     _, Message[turtleGraphics::"badopt", 
          Style[{"graphics" -> OptionValue["graphics"]}, "SBO"], "drawing option"]; 
        $Failed]]

draw[opts : OptionsPattern[]] := draw[$turtle, opts]

draw[args : __] :=
  Message[turtleGraphics::"badargs", "draw", Style[Defer @ {args}, "SBO"]]

This second example taken from the same package uses a fairly fussy pattern to reject invalid color specifications.

validColor = (_RGBColor | _GrayLevel | _Hue);

pc[t_Symbol, c : validColor] :=
  (If[isTurtle[t],
     setColor[t, colorToRGB[c]],
     Message[turtleGraphics::"notsymb", "pc"]];)

pc[c_] := pc[$turtle, c]

pc[args : ___] :=
  Message[turtleGraphics::"badargs", "pc", Style[Defer @ {args}, "SBO"]]

Sorry that I couldn't come up simpler examples, but functions thst need this kind argument or option validation tend more than a couple of lines. Note that draw expends more code on validation than on doing "real" work. It hands off the work to render which doesn't do validation because it was implemented under the expectation that any needed validation has already been done. render is not exposed for public use.

update

Here is a quotation from turtle graphics documentation. I add to make draw option processing more clear.


draw returns a turtle's track as a graphics object or as a list of graphics primitives. What is returned is controlled by the option "graphics".

  • "graphics" -> True or "graphics" -> Graphics. When either of these options is given, a graphics object is returned; this is the default. In this case, draw will also accept any options accepted by Graphics and pass them on when it calls Graphics. In those cases, when the "graphics" option is used to turn off this behavior, any graphics options will be ignored.

  • "graphics" -> False or "graphics" -> Identity. When either of these options is given, a list of graphics primitives is returned. This second option is useful when the intent is to combine the turtle track with other graphics primitives as part as a larger graphics object. n this case, draw will not accept any further options.

  • "graphics" -> f_Symbol or "graphics" -> f_Function. When an option of one of these two forms is given, the output of draw will be f[render[...]]. It should be clear that f must evaluate to a function of one variable.

    This third option form is supplied strictly for convenience; there is nothing that can be accomplished by using this form that can't be accomplished by post-processing the list returned when "graphics" -> False is given.

Side effects. If the turtle's pen is down when draw is called, the turtle's pen is raised and this has additional effects (see pu).

draw has an additional option, "reset", with the default value, True, meaning the turtle's track will be cleared when draw completes; when the alternative value, False, is given, the track will be preserved.

$\endgroup$

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