Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I'm wondering how to properly assess the type of arguments passed to functions defined with :=. I want my functions to be the most efficient, unambiguous and clear as possible.

When hacking together a function, I don't really care about the type of each argument passed to that function. I'll use an example taken from the documentation.

A first function draft would look like this:

>> g[x_] := Prime[x] - x

Which works fine as long as the type of the argument passed to it is compatible with Prime. The following is inappropriate behaviour, and should be fixed by changing the function definition:

>> {g[10], g["z"]}
<< {19, -"z" + Prime["z"]}

Here's where I don't know how to correctly proceed. Once I make sure my function works, by providing it arguments of the correct type, I can change its definition to

>> g[x_Integer] := Prime[x] - x

or

>> g[x_?IntegerQ] := Prime[x] - x

which both work as expected.

What's the difference between the two definitions, and which one should be used for maximum efficiency, robustness and cleanliness?

Example with one of my functions

This functions generates a set of points that, when passed to Line, draw a zig-zag. This case is more ambiguous than the previous.

I can define it this way, which works:

>> contactCoordinates[nCtc_Integer, x0_Integer, y0_Integer, 
   offset_?NumberQ] := 
    Table[{x, y0}, {x, x0, offset*nCtc, offset}]~Riffle~
     Table[{x, y0 + offset/2}, {x, x0 + offset/2, offset*nCtc, offset}]

I could also replace _Integer by _?IntegerQ, with no noticeable difference. The problem is with _?NumberQ. I can't define the function using offset_Number - I have either to use offset_?NumberQ or offset_Real. Is one better than the other?


  1. http://reference.wolfram.com/mathematica/tutorial/PatternsForSomeCommonTypesOfExpression.html
  2. Functions vs. patterns
  3. http://stackoverflow.com/q/8922966/1142160
share|improve this question
    
Related: mathematica.stackexchange.com/q/1835/121 –  Mr.Wizard Aug 13 '12 at 9:10

4 Answers 4

I think this question have gotten very good answers, but I just want to add my two cents to the topic.

f[_sheep] is a declarative typing, if something says it's a sheep, then it's a sheep, even if it doesn't make sense or will cause problems like: sheep[wolf[]]- Something which doesn't declare itself to be a Sheep isn't a Sheep even if you would suspect that it could perfectly well be assumed to be a sheep like wholly[sheep[]].

f[_?DuckQ] is duck typing. If it walks like a duck and quacks like a duck, then it's a duck. So for example if we define DuckQ[dog[]]=True then dog[] is a Duck, and can be given to any function assuming arguments of the type Duck[]

Declarative typing will be more efficient, since you can deduce everything from the heads of the arguments. Duck typing is more flexible, and allows you to do type checking without explicit declaring the type of everything. A good example is matrixQ. You could explicitly state that your data is a matrix and define

f[_matrix]:=....
f[matrix[{1,0,0},{0,1,0},{0,0,1}]]

But most consider it nicer to just write out the structure as a list of lists, and use duck typing to check if it's a matrix.

An additional element of this is that declaritive typing as implimented in Mathematica does not allow type hiarchies, so You cannot define your function to accept animals f[_animal]:=... and expect it to work for sheep[] and duck[] but this can be handled easily by an appropriate "duck typed" definition f[_?animalQ]:=... with animalQ[_sheep]=True;animalQ[_duck]=True. Again showing the flexibility of defining boolean checks.

share|improve this answer
    
nice analogy :) –  acl Aug 13 '12 at 20:33
    
@CHM ambiguous in what sense? Maybe it could be improved. –  jVincent Aug 13 '12 at 20:44
    
+1 just for the menagerie. –  murray Aug 13 '12 at 21:49

The differences between using _Integer and _?IntegerQ as to efficiency, robustness and cleanliness in my view are:

Efficiency

_Integer is more efficient. It belongs to what @LeonidShiffrin calls syntactic patterns and the evaluator doesn't need to be called to check if the pattern fits. On the other hand, ? and /; require a call to the evaluator

Robustness

Both have their issues. _?IntegerQ will leak evaluation in cases where you might not want to. For example

SetAttributes[f, HoldAll];
f[_?IntegerQ]:=8;

Now, evaluating f[Print@9] will print the number 9, and that wouldn't happen with _Integer. This isn't only an issue if your symbol has a holding attribute, because the user might always call the function using Unevaluated and expect it not to leak

_Integer, however, has the disadvantage of matching something like f[Integer[Plot[Sin[x], {x, 0, 10}]]] which is clearly not a good thing

The most robust solution I see is to fix IntegerQ using

f[_?(Function[i, IntegerQ@Unevaluated@i, HoldFirst])]:=8;

or to fix _Integer using

f[_Integer?(Function[i, AtomQ@Unevaluated@i, HoldFirst])]:=8;

If robustness is the priority, you probably can't avoid a call to the evaluator. It would be practical and neat to name a function

makeHolding[f_] := Function[Null, f@Unevaluated@##, HoldAll]
holdAtomQ= makeHolding[AtomQ]

and then just use f[_Integer?holdAtomQ], f[_Real?holdAtomQ], etc, or f[_?(makeHolding[IntegerQ])]

Cleanliness

Personally I like _Integer more in this regard but I think this is totally up to you


As to your example, all that comes to mind is the reminder that NumberQ won't match numbers like Pi while NumericQ will.

Also, as a sidenote, graphics primitives use real coordinates, and if you give them exact coordinates they have to save both versions of the numbers, in case you want to recover them. If you are not interested in keeping the original exact values in the front end, it may make sense to convert the parameters to approximate reals whatever form they came in. Just an idea

share|improve this answer
    
Quite thorough. +1 –  Mr.Wizard Aug 13 '12 at 9:11
    
Really clarifying. +1 –  Alexey Popkov Aug 13 '12 at 16:38

A key issue, I think, is that PatternTest must evaluate its argument to work, even if function holds its arguments. Thus, there can be a huge difference in the timing between the two. Here's an example:

pic := Plot3D[Sin[1/x] Cos[1/y], {x, -1, 1}, {y, -1, 1},
  PlotPoints -> 100];
SetAttributes[f1, HoldFirst];
SetAttributes[f2, HoldFirst];
f1[x_Integer] := Prime[x] - x;
f2[x_?IntegerQ] := Prime[x] - x;
f1[pic] // Timing
f2[pic] // Timing

{6.*10^-6, f1[pic]}

{1.43547, f2[pic]}

When unanticipated, this type of evaluation is sometimes called an evaluation leak. I would certainly use Pattern whenever possible, although PatternTest is more general.

share|improve this answer

I'll try to answer "What's the difference between the two definitions, and which one should be used for maximum efficiency, robustness and cleanliness?".

The PatternTest approach is significantly more general, since the test can be arbitrarily complicated. Thus, we may define a function only for positive arguments that are even:

ClearAll@f;
f[x_?(# > 0 && EvenQ[#] &)] := 5

f[-2]
f[2]
f[3]
(*
f[-2]
5
f[3]
*)

Conversely, the more general approach may be slower, eg

ClearAll[f, g];
f[x_?IntegerQ] := x
g[x_Integer] := x

f[5] // timeIt
g[5] // timeIt

Mathematica graphics

where timeIt is

ClearAll[timeIt];
SetAttributes[timeIt, HoldAll]
timeIt[expr_] := Module[{t = Timing[expr;][[1]], tries = 1},
    While[t < 1.,
    tries *= 2;
    t = AbsoluteTiming[Do[expr, {tries}];][[1]];
    ];
    Return[t/tries]]
share|improve this answer
    
do you know why timeIt /@ {f[5], g[5]} gives different answers vs. {timeIt[f[5]], timeIt[g[5]]} ? thanks –  Nasser Aug 13 '12 at 3:15
    
@NasserM.Abbasi timeIt is HoldAll. In your map version, f[5] and g[5] get evaluated before timeIt enters the game, because Map doesn't have a holding attribute. Try timeIt/@Unevaluated@{f[5], g[5]} –  Rojo Aug 13 '12 at 11:07

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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