Smart Pattern Test
You can temporarily deactivate the definitions of the symbol MyTypeQ
and define new ones, by using a Block
construction for g
. These new definitions are local to the Block
and will be in effect at any point during the execution of its body. (See for further details this tutorial.)
MyTypeQ[expr_] := (Pause[.5]; IntegerQ[expr]);
f[x_?MyTypeQ] := x;
g[x_?MyTypeQ] := Block[{MyTypeQ},
(* new local definition *)
MyTypeQ[_] := True;
(* body *)
{x, f[x]}
];
Usage:
f[1] // AbsoluteTiming
(* uses the global definition of MyTypeQ *)
(* {0.494262, 1} *)
g[1] // AbsoluteTiming
(* uses the global definition of MyTypeQ for g and the local one for f *)
(* {0.491009, {1, 1}} *)
f[1] // AbsoluteTiming
(* still uses the global definition of MyTypeQ *)
(* {0.499528, 1} *)
Update. The above code works fine when the function f
is called only with argument x
given to g
. If called with another, it will give an incorrect result:
ClearAll[g];
g[x_?MyTypeQ] := Block[{MyTypeQ},
MyTypeQ[_] := True;
{x, f[x], f[x/2]}
];
g[1]
(* {1, 1, 1/2} *)
g[1]
should return instead {1, 1, f[1/2]}
since 1/2
does not yield True
for the global definition.
An extension of the above code to account for such cases can be done with Internal`InheritedBlock
. As Block
, this symbol allows one to make temporary definitions; and unlike Block
, original definitions are kept.
ClearAll[g];
g[x_?MyTypeQ] := Internal`InheritedBlock[{MyTypeQ},
MyTypeQ[x] = True;
{x, f[x], f[x/2]}
];
The local definition of MyTypeQ
now applies only to x
. This prevents the issue mentioned:
g[1] // AbsoluteTiming
(* {0.991258, {1, 1, f[1/2]}} *)
Here, the pattern test is evaluated twice (one for g[1]
and one for f[1/2]
), rather than 3 times (the evaluation for f[1]
was avoided).
Additional comments. With the last code we are effectively temporarily caching the result of MyTypeQ[x]
during the evaluation of the body of g
. The natural alternative that follows would be to cache permanently all values, as Chip Hurst mentionned in his comment.
ClearAll[MyTypeQ, f, g];
MyTypeQ[expr_] := MyTypeQ[expr] = (Pause[.5]; IntegerQ[expr]);
f[x_?MyTypeQ] := x;
g[x_?MyTypeQ] := {x, f[x], f[x/2]};
g[1] // AbsoluteTiming
(* {1.00844, {1, 1, f[1/2]}} *)
Prefering one or the other approach will depend on the body of g
and how it is used. I quote the last paragraph of this tutorial:
There is of course a trade‐off involved in remembering values. It is faster to find a particular value, but it takes more memory space to store all of them. You should usually define functions to remember values only if the total number of different values that will be produced is comparatively small, or the expense of recomputing them is very great.
Is that fine? The trick is that once the test is passed the argument is wrapped with MyType
so it should pass the test automatically.
f[MyType[var_]] := "f"
f[var_?MyTypeQ] := f[MyType[var]];
g[MyType[var_]] := {"g", f[MyType[var]] , "g"}
g[var_?MyTypeQ] := g[MyType[var]];
MyTypeQ[expr_] := (Pause[.5]; True)
f[1] // AbsoluteTiming
{0.500496, "f"}
g[1] // AbsoluteTiming
{0.500114, {"g", "f", "g"}}