11
$\begingroup$

Recently, I learned that it is possible to assign "types" to function arguments in the definition of a function. Suppose I have a function stringFun that does some operation (e.g., StringSplit) on an input string str. I could define the function in this way:

stringFun[str_] := StringSplit[str]

Or, I could define the argument str_ as being of type String:

stringFun[str_String] := StringSplit[str]

What other argument "types" exist in Mathematica? Can I use, for example, Real, List, or Table? I am using Mathematica 7, and I am having some difficulty locating a list of "types" in the documentation. Thanks for your time.

$\endgroup$
3
  • 3
    $\begingroup$ Well, since every object in Mathematica has a head... you can always write your functions so that it only works on objects with certain heads. $\endgroup$ Sep 3, 2012 at 15:50
  • 1
    $\begingroup$ Related question $\endgroup$ Sep 3, 2012 at 18:56
  • $\begingroup$ I find the premise of this question potentially misleading, esp to newer users of Mathematica. (But it's still a good question to ask!!!) You can't '...assign "types" to function arguments...' but you can filter which arguments can be passed to the RHS of your function. This is an important distinction, IMO. $\endgroup$ Sep 4, 2012 at 3:26

3 Answers 3

19
$\begingroup$

The notation foo[x_bar] := ... merely states that you're defining the function foo only for arguments that have the head bar. Here's a silly example to show you that:

Clear@foo
foo[x_bar] := First@x
foo[bar[2, 3]]
(* 2 *)

foo[{2, 3}]
(* foo[{2, 3}] *)

Note that the head can be any symbol (function) and not necessarily types such as string/real/integer, etc. Since everything in Mathematica is an expression which has a Head and possibly several Parts, you can create your own patterns to work only with certain heads as in the example above.


Differences between patterns with specific heads (_h) and equivalent pattern tests (_?hQ)

Probably a more important distinction is the difference between specifying heads in patterns and pattern tests for the same head, i.e. between, say, x_Integer and x_?IntegerQ. On the surface, they behave the same:

Clear[f, g]
f[x_Integer] := x^2
g[x_?IntegerQ] := x^2

{f[4], g[4]}
(* {16, 16} *)

but they have two important distinctions that are worth highlighting

1. Behaviour in functions with the HoldAll attribute

If your functions have the HoldAll attribute (or any Hold* attribute that has an equivalent effect on the pattern), the arguments are not evaluated by default. The latter form (using PatternTest) forces evaluation of the argument and hence is useful in this case. For example, slightly modifying the above:

Clear[f, g]
SetAttributes[#, HoldAll] & /@ {f, g};
f[x_Integer] := x^2
g[x_?IntegerQ] := x^2

{f[2 + 2], g[2 + 2]}
(* {f[2 + 2], 16} *)

2. Calls to the main evaluator

As Leonid notes, matches (or not) for patterns of the type x_foo are easily established by the pattern matcher, which can be very fast. On the other hand, the presence of a pattern test passes the evaluation to the main evaluator and introduces a sub-evaluation during the pattern matching and this can result in a big performance hit. As a simple example, consider the following:

Cases[Hold@{2, {Print["Evaluated!"]}, Pause[1.]}, _Integer, ∞] // AbsoluteTiming
(* {0.000028, {2}} *)

You can see that using the _Integer pattern did not result in evaluation of either the Print statement or the Pause statement. Now compare:

Cases[Hold@{2, {Print["Evaluated!"]}, Pause[1.]}, _?IntegerQ, ∞] // AbsoluteTiming
(* Evaluated!
   Evaluated!
   Evaluated!
   {2.000453, {2}} *)

You can see that the Print statement was evaluated as Cases walked through the expression tree (three times in total) and the Pause statement was executed twice.

So the take away message is that if you want to do something like strong type checking, then avoid PatternTests.

$\endgroup$
4
  • 6
    $\begingroup$ +1, nice explanation. I would probably add that the behavior of functions with HoldAll attribute is but one example of a more general and simple principle: patterns of type x_h are purely syntactic and the fact of their match can be established by the pattern-matcher alone, while the presence of Condition and PatternTest signal the call to the main evaluator. That is, every pattern having them as its part(s) induces sub-evaluations during the pattern-matching, just to establish the fact of the match. $\endgroup$ Sep 3, 2012 at 18:49
  • 6
    $\begingroup$ This has also a considerable impact on performance, see e.g. this answer. This may become relevant in the context of typing and type-checking, since sometimes type-checks using PatternTest can bring an unacceptable performance hit. Finally, truly strong typing should not IMO use PatternTest since this makes it dependent on main evaluator, while using x_h makes this quite rigid and independent on anything outside the pattern-matcher. $\endgroup$ Sep 3, 2012 at 18:54
  • $\begingroup$ @LeonidShifrin Thanks, I'll add that info in shortly. I finally realize what you mean by your term "syntactic patterns" :) Even though I was aware of the behaviour of x_h and x_?h it never occurred to me that this was important enough to maintain the distinction, but now it's clear :) $\endgroup$
    – rm -rf
    Sep 3, 2012 at 20:09
  • $\begingroup$ No problem :). Just thought it would be useful to mention the general reason / mechanism. $\endgroup$ Sep 3, 2012 at 20:11
5
$\begingroup$

As J.M. pointed out: What you are matching is the Headof the expression. So basically you can match anything you like.

For example:

Head /@ {1, "a str", \[Pi], myWrap[x]}
(* ==>
{Integer, String, Symbol, myWrap}
*)

So you can define a function that only accepts "wrapped" expressions:

ClearAll[f]
f[exp_myWrap] := exp[[1]];
f /@ {1, "a str", Pi, myWrap[x]}

(* ==> {f[1], f["a str"], f[Pi], x} *)    
$\endgroup$
4
$\begingroup$

To save me having to remember the syntax each time, I have this current list of names in an initialization section (Or put this at the top of you notebook, or any where you want to use this each time):

(*definitions used for parameter checking*)

integerStrictPositive = (IntegerQ[#] && # > 0 &);
integerPositive = (IntegerQ[#] && # >= 0 &);
numericStrictPositive = (Element[#, Reals] && # > 0 &);
numericPositive = (Element[#, Reals] && # >= 0 &);
numericStrictNegative = (Element[#, Reals] && # < 0 &);
numericNegative = (Element[#, Reals] && # <= 0 &);
bool = (Element[#, Booleans] &);
numeric = (Element[#, Reals] &);
integer = (Element[#, Integers] &);

Then in the function definiitions, I use the above as

listClass[
  $size_?integer, $r0_?numericStrictPositive,
  .... ]:=Module[{},.....]

and so on.

This made it easier for me to use these checks since it a little easier to use as I do not have to look up the correct syntax each time as I did before.

I have a note in my FAQ on this with more documentation here http://alturl.com/a3p66

$\endgroup$
2
  • 1
    $\begingroup$ I presume you know the functions Positive[] and NonNegative[], and just chose not to use them? $\endgroup$ Sep 3, 2012 at 22:56
  • $\begingroup$ Well, SetAttributes[g, HoldAll]; integerStrictPositive = (IntegerQ[#] && Positive[#] &); g[x_?integerStrictPositive] := x^2; {g[5 + 10], g[5 - 10]} behaves as I expected it to... $\endgroup$ Sep 3, 2012 at 23:11

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.