Package Usage and Development » Advanced Features

Function Preparation

When making a production grade function there are a number of things we need to set:

  • Usage message templates
  • Syntax hints
  • Autocompletions
  • Bad argument protection

Mathematica doesn't make it terribly simple to do this, but neither does it make it terribly difficult. There are, perhaps, 5 or so points of boiler plate to be covered. We'll handle these in turn, by considering some function, packageFunction , which is defined as follows:

 Options[packageFunction]=Options[Panel];
packageFunction[
  style:"NewStyle"|"OldStyle", 
  cont:_String?(StringEndsQ["`"]),
  name_String?(StringMatchQ[LetterCharacter~~WordCharacter..]),
  ops:OptionsPattern[]
  ]:=
  Panel[
    StringForm["`` is a `` package from the context ``",
      name,
      style,
      cont
      ],
    ops
    ]

It doesn't do terrible interesting things, but it'll be illustrative:

 packageFunction["NewStyle", "ctxt`", "asd"]
342functionpreparation-8579650201645694798

This is how the function will start, and we'll incrementally add things to it

Usage Messages

Every symbol exposed to the public in a package should have a usage message. Not only does this give a usage hint if asked for, the system also uses it to format function templates that may be conveniently filled in.

For instance, if we look at the usage for MatrixQ we get the following:

 ?MatrixQ
MatrixQ[expr] gives True if expr is a list of lists or a two-dimensional SparseArray object that can represent a matrix, and gives False otherwise. MatrixQ[expr,test] gives True only if test yields True when applied to each of the matrix elements in expr.

The nice thing is, if we type MatrixQ and then CMDSHIFTK we'll get a function template that we just fill out:

 MatrixQ[expr,test]

We can set up a similar thing for our function. We take the call signature and strip all pattern constructs, but things like List constructs are totally fine to leave in.

 packageFunction[style, cont, name, ops]

Then we turn this into a string and add a space and a short description of what it does:

 "packageFunction[style, cont, name, ops] tells us about a package function"

Then once we assign this to the function's ::usage message we too can get that template:

 packageFunction::usage=
  "packageFunction[style, cont, name, ops] tells us about a package function";
?packageFunction
"packageFunction[style, cont, name, ops] tells us about a package function"
 packageFunction[style, cont, name, ops]

Syntax hints

When we type something that Mathematica knows is an incorrect usage of a function it lets us know by some syntax hint in the front end. There are four different types of hints it can give us:

  • Missing / Excess argument hints

If I have too few arguments:

 Plot[]

it inserts a red caret where it knows it need more arguments.

If I have too many:

 MatrixQ[a, b, c, d, e]

it highlights the excess ones (the last three) in red

  • Bad option names

If I have a function with Options and I feed it an invalid option (according to a predefined list) the front-end can let me know:

 Plot[x, {x, 0, 1}, BadOption->val]

that option name gets colored in Red

  • Local variable hints

If I have a function like Table or Solve the front-end can tell me which variables should be tagged as local. For instance in Table

 Table[x*y*z, {x, 0, 1}, {y, 0, 1}]

the iterator variables ( x and y ) get colored teal

In Solve

 Solve[x+y==1, x]

The solve variables ( x ) get colored teal

  • Misused equal signs

In things like If and Solve generally we want == , but sometimes accidentally type = . To help out with this rather subtle error Mathematica supplies a syntax hint highlighting this. For instance in:

 If[x=1, ...]

The = is colored red

Now, we can set all of these different syntax hints on our function via the function SyntaxInformation . I won't get too much into this, as the reference page actually documents this quite well, but for completeness sake there's an undocumented hint "OptionNames" that allows one to provide valid option names without needing an explicit OptionsPattern in the "ArgumentsPattern" .

We'll ignore that, though, and just note that all we need for our function to have good syntax hints is to give it:

 SyntaxInformation[packageFunction]=
  {
    "ArgumentsPattern"->{_, _, _, OptionsPattern[]}
    };

Autocompletions

This is probably the most useful bit of info we can attach to a function, especially if it's something like a router function, i.e. one that looks like:

 $methodAssoc=<|"Method1"->meth1, "Method2"->meth2, "SpecialMethod"->specMeth|>;
methodExecute[key_String?(KeyExistsQ[$methodAssoc, #]&), args___]:=
  Module[{fn, res},
    fn=$methodAssoc[key];
    res=fn[args];
    res/;Head[res]=!=fn
    ]

Autocompletions are a life-saver there, making it so one doesn't have to remember a plethora of string arguments for each method.

There is an undocumented way we can add autocompletion to our own functions via the front-end function FEPrivate`AddSpecialArgCompletion . This looks like:

 FrontEndExecute@
  FrontEnd`Value@
    Map[
      FEPrivate`AddSpecialArgCompletion,
      {
        "name"->{completions...}
        ...
        }
      ]

Where the completions are any of

  • {"val1", "val2", ...} a list of possible completions
  • 0 no completions
  • 2 absolute file path
  • 3 relative file path
  • 4 a color
  • 7 a package
  • 8 a directory
  • 9 an interpretable type
  • "MenuList*" any of the valid front-end menu lists (I won't list them all here, but they include styles and fonts)

For our function we only need to add a single autocompletion to the first argument, so we'd do that like:

 FrontEndExecute@
  FrontEnd`Value@
    FEPrivate`AddSpecialArgCompletion[
      "packageFunction"->{{"NewStyle", "OldStyle"}}
      ];

And now when we type

 packageFunction["

We'll be prompted to pick one of those choices

There is also a special package level file specialArgFunctions.tr that we can use to hold all these completions, but I tend to set them at load-time instead.

Bad Argument Protection

The final thing we need to configure for our function is the set of fallbacks if incorrect arguments are passed. We'll start with argument validation. So to our original definition we'll add one that assumes just the argument types are wrong, but the right number of arguments were passed. We'll use a template message that gets filled for all the parameters:

 packageFunction::invp="Value `` is invalid for parameter ``. Expected ``";
packageFunction[
  style_, 
  cont_,
  name_,
  ops:OptionsPattern[]
  ]:=
  Module[
    {
      par,
      val,
      expect
      },
    Which[
      !MatchQ[style, "NewStyle"|"OldStyle"],
        par="style";
        val=style;
        expect="\"NewStyle\" or \"OldStyle\"",
      !MatchQ[cont, _String?(StringEndsQ["`"])],
        par="context";
        val=cont;
        expect="a string ending in `",
      !MatchQ[name, _String?(StringMatchQ[LetterCharacter~~WordCharacter..])],
        par="name";
        val=name;
        expect="a string composed of letters and numbers"
      ];
    Null/;Message[packageFunction::invp, val, par, expect]
    ]

To this we'll also want one that tells us if the wrong number of arguments was passed. The heart of this will be the function System`Private`ArgumentsWithRules , which checks argument counts and emits a message if incorrect.

 packageFunction[
  args___
  ]:=
  (
    Null/;System`Private`ArgumentsWithRules[packageFunction[args], 3, Hold]
    )

Now we can test the various cases for our function:

  • Invalid argument
 packageFunction[1, 2, 3]
packageFunction::invp: Value 1 is invalid for parameter "style". Expected "\"NewStyle\" or \"OldStyle\""
 packageFunction[1,2,3]
  • Too few arguments
 packageFunction["NewStyle", "ctx`"]
packageFunction::argrx: packageFunction called with 2 arguments; 3 arguments are expected.
 packageFunction["NewStyle","ctx`"]
  • Too many arguments
 packageFunction["NewStyle", "ctx`", "asd", 1]
packageFunction::nonopt: Options expected (instead of 1) beyond position 3 in packageFunction["NewStyle","ctx`","asd",1]. An option must be a rule or a list of rules.
 packageFunction["NewStyle","ctx`","asd",1]

Finally with all this we have a production grade function that our users can easily use. This is a lot of work per function, but that's in some senses useful, encouraging us to use a small, tight public interface to our package and allowing most of the work to be done by our behind-the-scenes functions. There are also utilities out there to make this process simpler in general.

See Also: