I have moved the large addendum from my answer to How to program a F::argx message? to this post as I believe it is a better fit here. Please see that link for basic information before continuing.
Handling multiple messages with an auxiliary function
For full control of Message
generation 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 often has the advantage of less redundancy of the check patterns, while the latter has greater clarity and is easier to apply or retrofit.
A choice to be made is whether to issue only one error message or all messages that apply. Most System functions issue only one message if I recall correctly and your own example follows this apparent convention so I shall too.
Message generation needs to be a side-effect if the original expression is to be returned as is the case with built-in functions. Reference: Return the incomming expression on false
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"
Applied to the specific example in your question:
ClearAll[func, funcmsg]
Attributes[funcmsg] = {HoldAll};
func::arrayerr = "`1` and `2` must be valid 3D arrays.";
func::badpara = "`1` must be a valid real number in the interval (0,1].";
func::arg3 = "func called with `1` arguments; 3 arguments are expected.";
mat3Q = MatrixQ[#] && Length@First@# == 3 &;
func[sample1_?mat3Q, sample2_?mat3Q, c_] /; 0 < c <= 1 := c Join[sample1, sample2]
_func?funcmsg := Null
funcmsg @ _[x___] /; Length@{x} != 3 := Message[func::arg3, Length@{x}]
funcmsg @ _[_, _, c_] /; ! 0 < c <= 1 := Message[func::badpara, c]
funcmsg @ _[a_, b_, _] /; ! (mat3Q[a] && mat3Q[b]) := Message[func::arrayerr, a, b]
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]]
]
Or applied to the specific example in your question:
ClearAll[func, main, check]
Attributes[check] = {HoldAll};
func::arrayerr = "`1` and `2` must be valid 3D arrays.";
func::badpara = "`1` must be a valid real number in the interval (0,1].";
func::arg3 = "func called with `1` arguments; 3 arguments are expected.";
mat3Q = MatrixQ[#] && Length @ First @ # == 3 &;
func[args___]?check := main[args]
main[sample1_, sample2_, c_] := c Join[sample1, sample2]
check @ _[x___] /; Length@{x} != 3 := Message[func::arg3, Length@{x}]
check @ _[_, _, c_] /; ! 0 < c <= 1 := Message[func::badpara, c]
check @ _[a_, b_, _] /; ! (mat3Q[a] && mat3Q[b]) := Message[func::arrayerr, a, b]
check @ else_ := True
Message generation within argument testing
Though less controllable than the auxiliary function methods another useful option is to perform message generation directly from within argument tests. It can be cleaner and faster as it reduces redundant testing but it may obfuscate simple tests and patterns, and message generation from complicated definitions may be somewhat unpredictable as a given test may fail (and generate a message) multiple times.
For this example I shall also use the additional streamlining of Macros`SetArgumentCount
as described in How to program a F::argx message?.
func::arrayerr = "`1` must be a valid 3D array.";
func::badpara = "`1` must be a valid real number in the interval (0,1].";
Clear[func]
Macros`SetArgumentCount[func, 3]
mat3Q = MatrixQ[#] && Length@First@# == 3 || Message[func::arrayerr, #] &;
int01Q = 0 < # <= 1 || Message[func::badpara, #] &;
func[sample1_?mat3Q, sample2_?mat3Q, c_?int01Q] := c Join[sample1, sample2]
WRI
deal with the warning information when they develop built-in functions. $\endgroup$uniform
style to deal with warning informations? $\endgroup$uniform
style." $\endgroup$