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 have a simple function that is supposed to only accept numeric values (i.e. complex/real numbers and constant symbols e.g. Pi, E).

$$f(a,b,c)=a+b+c$$

Edit: I should have chosen a less simple function for this question as there might be approaches that will work for this simple function but not for functions in general (1-see end of question). Please think of a more complicated function, such as $$f(a,b,c)=a^2 sin(b) log(c)$$ when you're thinking of an answer.

I know that one can use _?NumericQ for each parameter such that only numeric values of that parameters are entered into the function (click here for more information on putting constrains on patterns).

Clear[f1]
f1[a_?NumericQ, b_?NumericQ, c_?NumericQ] := a + b + c

f1 @@@ {{1, 2, 3}, {x, y, z}, {1, y, z}, {x, 2, z}, {x, y, 3}}
(* {6, f[x, y, z], f[1, y, z], f[x, 2, z], f[x, y, 3]} *)

However, for functions with more than 1 variables, I'm way too lazy to add NumericQ after each parameter. Using /; at the end of the function definition works, but I feel it's still too long and I have to retype the name of the parameters (a,b,c) at the end.

Clear[f2, f3]
f2[a_, b_, c_] := a + b + c /; And @@ (NumericQ[#] & /@ {a, b, c})
f3[a_, b_, c_] := a + b + c /; VectorQ[{a, b, c}, NumericQ]

Is there any way to express the condition only once, and without having to type the list of parameters one more time? I know that this is a frivolous question borne out of sheer laziness but I'd love to hear your ideas.

(1) such as using the double underscore (BlankSequence) to apply NumericQ to any number of arguments passed to Plus (per Kuba's helpful suggestion). This is because these arguments have identical hierarchy in the function--thus having no need for parameters with different names to represent them--and because Plus can take any number of arguments.

Clear[f4]
f[a__?NumericQ] := Plus[a]

f @@@ {{1, 2, 3}, {x, y, z}, {1, y, z}, {x, 2, z}, {x, y, 3}}    
(* {6,f[x,y,z],f[1,y,z],f[x,2,z],f[x,y,3]} *)
share|improve this question
    
Thank you Kuba. This is a very neat way to apply conditionals to parameters for n-ary functions (eg. Plus) where the arguments have the same hierarchy, but I'm wondering if--and if so, how--to use this for more general functions. Again thank you for showing me this neat trick. –  seismatica Jul 4 at 6:48
5  
If you don't mind encasing arguments into a list, something like this would do for any generic function: bob[args : {a_, b_, c_, d_}] := a*b+c/d /; VectorQ[args, NumericQ] –  rasher Jul 4 at 6:50
    
@rasher Nice alternative. –  Mr.Wizard Jul 4 at 6:50
    
Thanks rasher! Your comment, for now, is the closest to what my inner sloth envisions. –  seismatica Jul 4 at 6:56
4  
It seems this works test[PatternSequence[a_, b_, c_]?NumericQ] := {a, b, c} but I'm not exactly sure why :) –  Kuba Jul 4 at 6:57

1 Answer 1

up vote 15 down vote accepted

Ramblings

Arguments of the left-hand-side head are evaluated in the course of function definition, therefore you can use a utility function that constructs the patterns that you want. For example:

SetAttributes[nq, HoldFirst]
Quiet[
 nq[s_Symbol] := s_?NumericQ
]

Now:

ClearAll[f]

f[nq @ a, nq @ b, nq @ c] := a + b + c

Definition[f]
f[a_?NumericQ, b_?NumericQ, c_?NumericQ] := a + b + c

Doing this you lose the nice syntax highlighting shown in the original.

If you Block all Symbols you could even Map the utility function, e.g.:

Block[{f, a, b, c},
  Evaluate[nq /@ f[a, b, c]] := a + b + c
]

This hardly feels like a clean solution however. Perhaps you merely want something shorter than verbatim NumericQ? At risk of a tautology you could always do something like:

ClearAll[f]

q = NumericQ;
f[a_?q, b_?q, c_?q] := a + b + c

But this requires you to keep the Global definition q or it will break as it is not expanded to NumericQ in the definition itself:

Definition[f]
f[a_?q, b_?q, c_?q] := a + b + c

Metaprogramming approach

Another approach would be to write a function to modify all Pattern objects on the left-hand-side at the time of assignment. Something like:

SetAttributes[defWithTest, HoldFirst]

defWithTest[(s : Set | SetDelayed)[LHS_, RHS_], test_] := 
  s @@ Join[Hold[LHS] /. p_Pattern :> p?test, Hold[RHS]]

Now:

ClearAll[f]

defWithTest[
  f[a_, b_, c_] := a + b + c,
  NumericQ
]

Definition[f]
f[a_?NumericQ, b_?NumericQ, c_?NumericQ] := a + b + c

Proposed solution

As Kuba and rasher show in the comments you could also use clever alternatives to the explicit form f[a_?NumericQ, b_?NumericQ, c_?NumericQ]. Inspired by those comments I propose:

SetAttributes[numArgsQ, HoldFirst]
numArgsQ[_[___?NumericQ]] := True

Now:

ClearAll[f]

f[a_, b_, c_]?numArgsQ := a + b + c

Test:

f[1, 2, 3]
f["a", 2, 3]
6

f["a", 2, 3]

For examples of advanced argument testing with an emphasis on messages please see:

Note how (some) internal functions pass additional argument checking (and message generation) to an auxiliary function, e.g. ChartArgCheck, much as I did in the minimal application of numArgsQ above.

share|improve this answer
    
Leave it you you to make it elegant... +1 as always. –  rasher Jul 4 at 7:02
    
@rasher Thank you. :-) –  Mr.Wizard Jul 4 at 7:04
1  
Very nice. Fun fact: One of the first hit googling for "wizard rambling" is youtube.com/watch?v=pEMXYJeCNPY –  Yves Klett Jul 4 at 11:58
    
@Yves Interesting, though I didn't understand a lot of it. –  Mr.Wizard Jul 4 at 18:20
    
The lyrics are below the video ;-) Will remove if you deem it too whimsical. –  Yves Klett Jul 4 at 18:53

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.