8
$\begingroup$

I'd like to make my own "typed function" in WolframScript. Suppose the type we desire is called money, and we'd like to make a function called doubleOurMoney which doubles any money argument but throws an error on everything else. How do we do this?

Attempt: This isn't quite right but perhaps is a good starting point:

doubleOurMoney[money[x_Real]] := money[2 x]
doubleOurMoney[money[2.0]]         (* Should return money[4.], and does. *)

doubleOurMoney[money["nonsense"]]  (* How do we make this throw an error? *)
doubleOurMoney[2]                  (* How do we make this throw an error? *)
doubleOurMoney["nonsense"]         (* How do we make this throw an error? *)
doubleOurMoney[symbol]             (* How do we make this throw an error? *)

Questions:

  1. How do we throw "type errors" in this context?
  2. Right now money accepts real numbers as arguments; how could we make it take any "number" as an argument (Integer, etc) but throw an error on everything else?
  3. Is my attempt at "typed WolframScript" an idiomatic approach to type-checking in Wolfram? If not, what is the idiomatic approach here to check that inputs to a function are sensible?
$\endgroup$
2

1 Answer 1

11
$\begingroup$

Here is an idiom used by many built-in functions to achieve this effect:

doubleOurMoney::badarg = "`1` should be of the form money[number]";
doubleOurMoney[money[x_?NumericQ]] := money[2 x];
doubleOurMoney[args___] := (
    Message[doubleOurMoney::badarg, args];
    Null /; False
)

Here I'm using _?NumericQ instead of _Real to make your function work on all numeric input. That first definition will only fire for numeric inputs of the right form, but the second definition will work for any input. The second definition will issue a message telling the user what type of input is needed:

doubleOurMoney[money[2]]
(* money[4] *)

doubleOurMoney[3]

During evaluation of doubleOurMoney::badarg: 3 should be of the form money[number]
(* doubleOurMoney[3] *)

doubleOurMoney["bob"]

During evaluation of doubleOurMoney::badarg: bob should be of the form money[number]
(* doubleOurMoney["bob"] *)

That last bit of trickery is so that your function, after issuing a message, returns unevaluated. You could replace the Null /; False with $Failed just as easily.

$\endgroup$
3
  • $\begingroup$ This is great. Is there a way to declare separately (at the beginning) that money only takes form money[x_?NumericQ] and for all other money[args___] we throw an error? And then that way we don't have to do it within doubleOurMoney but can just say doubleOurMoney takes a money[..] (however it is defined). $\endgroup$
    – George
    Jun 26, 2019 at 15:58
  • $\begingroup$ That is a bit different - you could make a definition for money, like money[ arg: Except[_?NumericQ] ] := (Message[ money::badarg];$Failed). In that case, money would return $Failed for any non-numeric input. $\endgroup$
    – Jason B.
    Jun 26, 2019 at 16:24
  • $\begingroup$ @JasonB. or even better use SetValid on valid data (and only run the check on stuff without the bit) and then doubleOurMoney need only check for the valid bit. $\endgroup$
    – b3m2a1
    Jun 26, 2019 at 18:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.