24

How useful is it to program a user-built function in a package to produce a red warning message F::argx if you give the wrong number of arguments to that function? How do I do this?

Chris K
  • 20,207
  • 3
  • 39
  • 74

4 Answers4

26

The large addendum on handling multiple messages as built-ins do has been moved to a separate post; please see the link below for advanced message handling options.


Macro package function SetArgumentCount

In recent versions there is an undocumented function Macros`SetArgumentCount that specifically automates creation of a definition for an argument Message. Examples:

Macros`SetArgumentCount[foo, 2]

foo[1]
foo[1, 2, 3]

foo::argr: foo called with 1 argument; 2 arguments are expected.

foo::argrx: foo called with 3 arguments; 2 arguments are expected.

Macros`SetArgumentCount[foo, {2, 4}]

foo[1]
foo[1, 2, 3, 4, 5]

foo::argbu: foo called with 1 argument; between 2 and 4 arguments are expected. >>

foo::argb: foo called with 5 arguments; between 2 and 4 arguments are expected. >>

(Returned input intentionally omitted.)


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.

Synax highlighting

SyntaxInformation can be used to create the red syntax highlighting present in many built-in functions. For a full description please see:

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
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • You said it didn't produce the same behaviour as in built-ins and that using condition was better... I just don't see it in the example – rm -rf Apr 15 '13 at 21:01
  • 3
    @rm-rf Oh. Your catch definition actually matches and returns Message[f::argx, "f", Length@{x} + 1] or rather the Null it produces; my catch definition does not match and produces the message as a side-effect, and therefore the original input is returned. This is how most internal definitions are written. – Mr.Wizard Apr 15 '13 at 21:02
  • 1
    Ah, it keeps the input unevaluated. +1 – rm -rf Apr 15 '13 at 21:06
  • btw, I just remembered why I don't use this in my packages... if you have different messages being thrown based on the form of the input (as I often have), then throwing messages as a side-effect of not matching the form will result in all messages being thrown. – rm -rf Apr 16 '13 at 14:32
  • @rm-rf please see my update and tell me if it makes sense as written. – Mr.Wizard Apr 16 '13 at 19:43
  • Thanks for the update! I'm a little busy now, but will definitely read it later in the day – rm -rf Apr 16 '13 at 20:02
  • @Mr.Wizard, you expose here a great summary of techniques, thank you for this. – faysou Apr 16 '13 at 21:42
  • @Faysal Thanks, and you're welcome. It's been a while since I've used these methods in my own code so hopefully I didn't make too many mistakes here; please let me know if you find any. – Mr.Wizard Apr 16 '13 at 22:02
  • 1
    Sidenote: ArgumentCountQ lets you be more declarative and takes care of selecting the message argx, argtu, argrx, according to the number of arguments required. E.g, f[i___] /; ArgumentCountQ[f, System`FEDump`NonOptionArgCount[{i}], {2, 5}] := Null – Rojo Jul 21 '13 at 11:37
  • Just read through your "fall-through" method. Clever. – rcollyer Aug 13 '13 at 15:40
  • I thought func[___]?funcmsg was clever before, I think _func?funcmsg is superior. – rcollyer Jul 15 '14 at 13:54
  • If I want to define a head f: f[a_,b_*c_]:=... ; f[a_*b_,g[c_]]:=...;How to define my syntax-checking function? – WateSoyan May 15 '15 at 11:02
  • @WateSoyan I would need more information to properly answer that. Why don't you post a new question explaining your function and what you wish to accomplish regarding syntax-checking and message generation? – Mr.Wizard May 15 '15 at 11:45
  • Though my function here is fabricate , indeed I often have this need. – WateSoyan May 15 '15 at 12:08
  • Dear Mr.Wizard, In the section "Basic usage", f[x___] /; Message[f::argx, "f", Length@{x}] := Null, However, I cannot understand why f[1,2,3] gives the warning information. Could you help me to understand it? Thks:) – xyz Jun 22 '15 at 07:32
  • Accodrding the usage of /; in DOC, lhs:=rhs/;test is a definition to be used only if test yields True. – xyz Jun 22 '15 at 07:41
  • 2
    @ShutaoTang The Message expression is evaluated as part of the Condition. Message itself returns Null, therefore the condition does not match, but by that time the message has already been printed. The RHS is irrelevant in this case. – Mr.Wizard Jun 22 '15 at 07:42
  • In addition , I cannot find the definition HiddenOptions in the auxiliary function optCheck – xyz Jun 22 '15 at 07:44
  • @ShutaoTang hiddenOptions is not really significant here; it holds internal options that are not intended to be set by the user. In version 10.1.0 you can see these for ArrayPlot with DownValues[Graphics`ArrayPlotDump`Private`hiddenOptions]. – Mr.Wizard Jun 22 '15 at 07:51
  • @Mr.Wizard, In my V10.1, DownValues[Graphics`ArrayPlotDump`Private`hiddenOptions] gives the result{} – xyz Jun 22 '15 at 08:02
  • Dear Mr.Wizard, Now I have a small request. Could you use the Catch-all method or Fall-through method to deal with my qestion like the self-contained example you give in the end of you answer. And make it has the effect like this – xyz Jun 22 '15 at 08:10
  • @ShutaoTang I'll take another look at try to provide a useful example. – Mr.Wizard Jun 22 '15 at 09:12
  • @ShutaoTang I am too sleepy to do this properly right now, and also this answer needs updating too. I'll try to get around to it tomorrow. – Mr.Wizard Jun 22 '15 at 10:11
  • @Mr.Wizard, OK, but now it is day time in China:) – xyz Jun 22 '15 at 10:13
16

As acl points out, this post shows you how to setup error highlighting for invalid number of arguments. Coming to the actual error messages used, there are three built-in messages attached to General, that can be used for your own functions as well. These are argx, argrx and argt:

General::argx
(* "`1` called with `2` arguments; 1 argument is expected." *)

General::argrx
(* "`1` called with `2` arguments; `3` arguments are expected." *)

General::argt
(* "`1` called with `2` arguments; `3` or `4` arguments are expected." *)

You can attach these messages to your own functions (any message defined for General can be used for any other symbol) like in the following example (shown only for argx):

ClearAll@f
SyntaxInformation[f] = {"ArgumentsPattern" -> {_}};
f[1] := True
f[_] := False
f[_, x__] := Message[f::argx, "f", Length@{x} + 1]

rm -rf
  • 88,781
  • 21
  • 293
  • 472
5

There's a nice internal method for this that has existed since at least 10.4: System`Private`ArgumentsWithRules.

You can use it to define a nicely handled function like so:

f[args___] :=
 With[
  {
   a =
    System`Private`ArgumentsWithRules[
     f[args](*function with args to gather from*), 
     {
      0(*min length*),
      1(*max length*)
      }, 
     Hold(*head to wrap on the lists*)
     ]
   },
  yourFunctionBody /; Length[a] > 0
  ]

f[1, 2]

f::argt: f called with 2 arguments; 0 or 1 arguments are expected.

f[1, 2]

Even better, if f has Options this detects it:

Options[f] = {"a" -> "b"};
f[1, 2]

f::nonopt: Options expected (instead of 2) beyond position 1 in f[1,2]. An option must be a rule or a list of rules.

f[1, 2]

This makes it useful extension on ArgumentCountQ:

ArgumentCountQ[f, 3, 1, 2]

f::argt: f called with 3 arguments; 1 or 2 arguments are expected.

False
b3m2a1
  • 46,870
  • 3
  • 92
  • 239
  • Do you know what is the difference between System`Private`Arguments and System`Private`ArgumentsWithRules? They seem to behave in the same way. Also, it seems to color the f black in the front end just be calling it like System`Private`ArgumentsWithRules[f[a, b], {1, 2}, g] from a fresh kernel. – QuantumDot Aug 06 '18 at 14:24
  • @QuantumDot ArgumentsWithRules provides options checking for us. See this and this – b3m2a1 Aug 06 '18 at 17:34
  • Hmm.. it seems Arguments itself can provide option checking: Options[f] = {opt1 -> x}; System`Private`Arguments[f[a, b, c], {1, 2}, g] – QuantumDot Aug 06 '18 at 19:45
  • @QuantumDot ah hmm, I remember the difference being subtle. It might be just in the message or the return or something. Just pick one and be consistent I figure. – b3m2a1 Aug 06 '18 at 20:03
1

Thats what I learned.
To see if my function has just 2 arguments, not 1 argument and no more the 2 arguments I did these steps:
Define the warnings

General::twoplus = "f called with to much arguments, 2 argument expected.";  
General::twominus = "f called with 1 argument, 2 arguments expected.";  
f[_, _, x__] := Message[f::twoplus, "f", Length@{x} + 1]  
f[x__] := Message[f::twominus, "f", Length@{x} + 1]  

Define the function

f[x_, y_] := x + y + 1;  

Call the function

f[1]  
f::twominus: f called with 1 argument, 2 arguments expected.  

f[1, 2]  
4  

f[1, 2, 3]  
f::twoplus: f called with to much arguments, 2 argument expected.  
  • Some things to consider: (1) Your messages have no placeholders; nothing is being filled in from the second and following arguments of Message. You can use merely Message[f::twominus] if you want the message printed verbatim. (2) You may wish to define the message for f rather than for General unless you intend to use the same messages for other functions as well. – Mr.Wizard Apr 18 '13 at 09:26