Available messages
The message ::argx
is one of the general messages intended for use with any function. These have the special property of being called for any symbol used (placed left of ::
):
Message[foo::"argx", "foo", 2, 3]
foo::argx: foo called with 2 arguments; 1 argument is expected. >>
Use Messages[General]
to see a list of these messages.
Basic usage
You may have noticed that the method shown in R.M's answer doesn't produce behavior that exactly matches internal functions such as Plot
, which echo bad input:
Plot[1, 2, 3, 4]
Plot::nonopt: Options expected (instead of 4) beyond position 2 in Plot[1,2,3,4]. An option must be a rule or a list of rules. >>
Plot[1, 2, 3, 4]
To get this you behavior you can generate the Message
as a side-effect, as in Condition
:
ClearAll@f
SyntaxInformation[f] = {"ArgumentsPattern" -> {_}};
f[1] := True
f[_] := False
f[x___] /; Message[f::argx, "f", Length@{x}] := Null
Now:
f[1, 2, 3]
f::argx: f called with 3 arguments; 1 argument is expected. >>
f[1, 2, 3]
The final no-match rule can also be written:
x_f /; Message[f::argx, "f", Length @ Unevaluated @ x] := Null
Handling multiple messages
R.M comments that he doesn't use this form in packages because with multiple messages defined all will be issued. Often this is desired as if there are multiple problems with the arguments it makes sense to inform the user of all of them at once. If however more control is needed, while retaining the canonical behavior of returning an unmatched expression, an axillary function can be used to handle argument testing and message generation.
This axillary function can be used either:
to check the arguments of the primary definition including correct arguments
applied only to a fall-through definition for those that did not match the primary definition
In my opinion the former has the advantage of less redundancy of the check patterns, while the latter has greater clarity and is easier to apply (or retrofit).
Fall-through method
Here is a simple example of the fall-through method. Both a General
and user-created message are used for the sake of illustration. Note that to reduce redundancy I used no actual argument test in funcmsg
but rather the problem is assumed based on the fact that the primary definition did not match. This may not be as robust as desired.
ClearAll[func, funcmsg]
Attributes[funcmsg] = {HoldAll};
func::"bad" = "Argument `1` is not a `2`.";
func[a : {__List}, i_: Automatic] := "operate"
func[___]?funcmsg := Null
funcmsg[f_[]] := Message[f::"argt", HoldForm[f], 0, 1, 2]
funcmsg[f_[_] | f_[_, _]] := Message[f::"bad", 1, "list of lists"]
funcmsg[x : f_[_, _, __]] :=
Message[f::"argt", HoldForm[f], Length @ Unevaluated @ x, 1, 2]
Now any calls that do not match func[a : {__List}, i_: Automatic]
(the primary definition) will be passed to the funcmsg
function for messages to be issued. Examples, with echoed (returned) input expressly omitted:
func[]
func::argt: func called with 0 arguments; 1 or 2 arguments are expected. >>
func[1, 2]
func::bad: Argument 1 is not a list of lists.
func[{{1}, {2}}, 3]
"operate"
Catch-all method
A variation of the method above to apply the message function in all calls rather than those that fail. This is used by internal functions such as ArrayPlot
, BarChart
, Histogram
and PolarPlot
. Examples:
e : ArrayPlot[args___] /; checkArgsOptions[e, 0, 1, hiddenOptions[ArrayPlot]] :=
With[{caller = ArrayPlot,
caught = Catch[ArrayPlotInternal[False, getArgs[args], getOpts[args]]]},
caught /; caught =!= $Failed]
BarChart[a : PatternSequence[___, Except[_?OptionQ]] | PatternSequence[],
o : OptionsPattern[]]?ChartArgCheck :=
With[{res = Catch[iBarChart[BarChart, a, o], "ParseNoData" | "ChartingError", $Failed]},
res /; Head[Unevaluated[res]] =!= $Failed]
Histogram[a : PatternSequence[___, Except[_?OptionQ]] | PatternSequence[],
o : OptionsPattern[]]?HistArgCheck :=
With[{res = Catch[iHistogramLayer1[a, o], "ChartingError", $Failed]},
res /; Head[Unevaluated[res]] =!= $Failed]
The definition of ChartArgCheck
used by BarChart
looks like this:
Attributes[ChartArgCheck] = {HoldAll}
ChartArgCheck[b : f_[{}, opts : OptionsPattern[]]] := True
ChartArgCheck[b : f_[{{} ..}, opts : OptionsPattern[]]] := True
ChartArgCheck[b : f_[args___, opts : OptionsPattern[]]] :=
Block[{len}, len = Length[Unevaluated[{args}]];
If[len <= 1, ArgumentCountQ[f, len, 1, 1],
Message[f::"nonopt", Last[Function[z, "HoldForm"[z], HoldAll] /@ Unevaluated[{args}]],
1, "HoldForm"[b]];
False, False]] && optCheck[b]
Note that in the third definition a Message
may be produced, and that it additionally calls optCheck
which may also produce a Message
from its definition:
Attributes[optCheck] = {HoldAll}
optCheck[b : f_[args___, opts : OptionsPattern[]]] :=
Module[{bad, good}, good = Join[Options[f], HiddenOptions[f]];
bad = FilterRules[{opts}, Except[good]];
If[Length[bad] > 0, Message[f::"optx", First[bad], "HoldForm"[b]];
False, True, True]]
Note that in each case (ArrayPlot
, BarChart
, Histogram
) the arguments are passed to an inner function that does that actual processing. This often is necessary to catch all the argument patterns desired while also handling correct arguments properly.
Here is a simplified, self-contained example:
ClearAll[foo, bar, check]
Attributes[check] = {HoldAll};
foo::"bad" = "Argument `1` is not a `2`.";
foo[args___]?check := bar[args]
bar[a_, i_: Automatic] := "operate"
check[f_[]] := Message[f::"argt", HoldForm[f], 0, 1, 2]
check[f_[Except[{__List}], ___]] := Message[f::"bad", 1, "list of lists"]
check[x : f_[___]] := With[{argln = Length @ Unevaluated @ x},
If[argln < 3, True, Message[f::"argt", HoldForm[f], argln, 1, 2]]
]