Downvalues vs. Scoping for Functions

Regarding my recent question on using a default value for a function argument when a pattern was not met yielded some interesting answers, but the general consensus was "Yes this can be done, but there are better options."

Assuming the same conditions that certain function arguments (rt and pt in this case) should default to zero when less than zero, the three main options are as follows:

Downvalues:

func[qi_, dei_, b_, dmin_, rt_, pt_, t_] :=
func[{qi, dei, b, dmin, Max[0, rt], Max[0, pt], t}]

func[{qi_, dei_, b_, dmin_, rt_, pt_, t_}] := (*do stuff*)


Or...

func[qi_, dei_, b_, dmin_, rt_?Negative, pt_, t_] := func[qi, dei, b, dmin, 0, pt, t]
func[qi_, dei_, b_, dmin_, rt_, pt_?Negative, t_] := func[qi, dei, b, dmin, rt, 0, t]

func[qi_, dei_, b_, dmin_, rt_, pt_, t_] :=(*do stuff*)


Scoping:

func[qi_, dei_, b_, dmin_, rt_, pt_, t_] := Module[{rtt = Max[0,rt], ptt = Max[0,pt]},
(*do stuff with rtt and ptt instead*) ]


What are the pros and cons of each function definition? Which is the most efficient?

-
 Sorry I forgot to update my old answer but you'll get more diverse opinions this way anyhow. – Mr.Wizard♦ Feb 25 at 15:57 @Mr.Wizard No big deal. – kale Feb 25 at 16:00 Methink you've lost the braces around some functions' arguments. I'm not editing your question because you already rolled back one edit. But please recheck – belisarius Feb 25 at 16:41 Your function with _?Negative doesn't do what the other does, for instance if you pass rt=-3; pt=1 it will be evaluated as rt=0; pt=0 instead of rt=0;pt=1 – ssch Feb 25 at 17:16 @belisarius Yep, sorry. Some extra braces made it in there. Edited... – kale Feb 25 at 18:47
show 1 more comment

To start the discussion I'll look at timings. A simplistic test indicates that the DownValues method is somewhat faster, and this agrees with my past experience as well.

f1[a_, b_, c_, d_] := f1[{a, b, Max[c, 0], Max[d, 0]}]
f1[{a_, b_, c_, d_}] := a + b + c + d

f2[a_, b_, c0_, d0_] := With[{c = Max[c0, 0], d = Max[d0, 0]}, a + b + c + d]

a = RandomInteger[{-5, 5}, {1*^6, 4}];

f1 @@@ a // Timing // First
f2 @@@ a // Timing // First


1.872

2.012

Note that I changed Module to With. This should always be faster when it can be used.
For comparison:

f2m[a_, b_, c0_, d0_] := Module[{c = Max[c0, 0], d = Max[d0, 0]}, a + b + c + d]

f2m @@@ a // Timing // First


3.791

-
 Wow... I wasn't aware that Module is so much slower! – sebhofer Feb 25 at 18:20 @sebhofer Yes, for simple (fast) functions the overhead of Module is significant. With does not let you modify variables. Block is faster than Module but you have to be very careful to avoid unintended consequences. You can often combine e.g. Module outside the definition of the function and Block inside for something reasonably fast. – Mr.Wizard♦ Feb 26 at 1:55