Skip to main content

error - Force a function to report argument type mismatches


As a relative newcomer to Mathematica, I am still frequently caught out by coding errors that, at least by default, MMA does not indicate to the front end, in particular when I define a function and the argument types and then accidentally pass an argument of the wrong type.


For example, if I define


f[x_Integer] := x + 1;


and then call


f[{1}]


it is returned unevaluated because {1} has Head "List" and "Integer" is required.


This sort of thing tends to happen to me when I first test a larger and more complex function where other errors are to be expected, in which case my attention usually goes directly to the code.


Question: How can I write functions and declare the argument types on which the rest of the code may depend and let MMA tell me when I have passed an argument of the wrong kind?




Answer



One way to write a function with argument checking is to write subroutines for your function f that handles the actual calculation, and then do the argument checking in the main routine for f.


As a highly simplified example, let's call the subroutines for f intF:


intF[x_Integer] := x + 1
intF[x_ /; ArrayQ[x, _, IntegerQ]] := x + 1

so that f should only work for an integer or an array of integers.


We can then define error messages that f can emit:


f::nlst = "The argument `1` is not an integer or an array of integers.";


From that, you can write f like this:


f[x__] := Module[{nargs, res},
nargs = Length[{x}];
res /; If[nargs == 1,
If[Head[res = intF[x]] =!= intF,
True,
Message[f::nlst, x]; False],
Message[f::argx, f, nargs]; False]]

The main point here is the use of Condition[] (/;) to do the argument checking; recall that in the expression expr /; test, test should be something that evaluates to True or False, and expr will only then be evaluated.



In this example, the first check is a simple argument count, done by checking if {x} has length 1 (i.e. f was only passed one argument). If the test fails, then the part Message[f::argx, f, nargs]; False gets evaluated, and since the last result is False, we get no evaluation of res.


Note, however, that I did not define the message f::argx. In this case, the message text is taken from General::argx.


If the check of the argument count is passed, we get to the inner conditional. In there, we evaluate Head[res = intF[x]] =!= intF. Two things are done here: the result of the subroutine intF[x] is assigned to res, after which it is checked if evaluation occurred (i.e. the head of res is no longer intF). If this test is passed, we get the ultimate result True for the condition, and thus the previously evaluated res is returned. Otherwise, Message[f::nlst, x]; False gets evaluated.


With this, f[3] and f[{{1}, {2}}] will evaluate as usual, but f[2.] and f[{{5, 6}, {4}}] will throw the message f::nlst, while f[1, 9] will throw the message f::argx.


Comments

Popular posts from this blog

front end - keyboard shortcut to invoke Insert new matrix

I frequently need to type in some matrices, and the menu command Insert > Table/Matrix > New... allows matrices with lines drawn between columns and rows, which is very helpful. I would like to make a keyboard shortcut for it, but cannot find the relevant frontend token command (4209405) for it. Since the FullForm[] and InputForm[] of matrices with lines drawn between rows and columns is the same as those without lines, it's hard to do this via 3rd party system-wide text expanders (e.g. autohotkey or atext on mac). How does one assign a keyboard shortcut for the menu item Insert > Table/Matrix > New... , preferably using only mathematica? Thanks! Answer In the MenuSetup.tr (for linux located in the $InstallationDirectory/SystemFiles/FrontEnd/TextResources/X/ directory), I changed the line MenuItem["&New...", "CreateGridBoxDialog"] to read MenuItem["&New...", "CreateGridBoxDialog", MenuKey["m", Modifiers-...

How to thread a list

I have data in format data = {{a1, a2}, {b1, b2}, {c1, c2}, {d1, d2}} Tableform: I want to thread it to : tdata = {{{a1, b1}, {a2, b2}}, {{a1, c1}, {a2, c2}}, {{a1, d1}, {a2, d2}}} Tableform: And I would like to do better then pseudofunction[n_] := Transpose[{data2[[1]], data2[[n]]}]; SetAttributes[pseudofunction, Listable]; Range[2, 4] // pseudofunction Here is my benchmark data, where data3 is normal sample of real data. data3 = Drop[ExcelWorkBook[[Column1 ;; Column4]], None, 1]; data2 = {a #, b #, c #, d #} & /@ Range[1, 10^5]; data = RandomReal[{0, 1}, {10^6, 4}]; Here is my benchmark code kptnw[list_] := Transpose[{Table[First@#, {Length@# - 1}], Rest@#}, {3, 1, 2}] &@list kptnw2[list_] := Transpose[{ConstantArray[First@#, Length@# - 1], Rest@#}, {3, 1, 2}] &@list OleksandrR[list_] := Flatten[Outer[List, List@First[list], Rest[list], 1], {{2}, {1, 4}}] paradox2[list_] := Partition[Riffle[list[[1]], #], 2] & /@ Drop[list, 1] RM[list_] := FoldList[Transpose[{First@li...

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...