10
$\begingroup$

I defined two functions f and g which accept some argument which must be of some type, so I defined befor MyTypeQ that returns true if the pattern maches. Then the definitions:

f[var_?MyTypeQ]:= ...

this function is used also (but non only) to define g:

g[var_?MyTypeQ]:= (...) f[var] (...)

that is, f is used inside.

Now, since the test MyTypeQ can be a bit cumbersome, I would like to avoid to repeat it many times. I was wandering whether in the definition of g once var has passed the MyTypeQ test, is tested again by means of f that sits inside the definition of g.

Is there an elegant method to avoid this without defining two version of g one "safe" (i.e. with MyTypeQ test) to be used outside g and the otehr "unsafe" (i.e. without MyTypeQ test) to be used inside g?

$\endgroup$
10
$\begingroup$

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.

$\endgroup$
  • $\begingroup$ The problem here can occur when g calls e.g. h[] where h internally needs MyTypeQ. Nevertheless +1 ;) $\endgroup$ – Kuba Sep 12 '16 at 16:06
  • $\begingroup$ Yes, there is actually another issue if f is called with another argument. I'm going to update my answer with an additional code. $\endgroup$ – user31159 Sep 12 '16 at 16:15
7
$\begingroup$

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"}}
$\endgroup$
  • $\begingroup$ @Xavier evaluation when? $\endgroup$ – Kuba Sep 12 '16 at 13:57
  • $\begingroup$ @Xavier that was the point, only the functions that performed the test was supposed to wrap MyType. I've reverted old code because the shorter one would lead to MyType[MyType[...]] nesting in general. $\endgroup$ – Kuba Sep 12 '16 at 14:05
  • $\begingroup$ Yes, this was my point of concern. Didn't see the previous version, it is better I agree. $\endgroup$ – user31159 Sep 12 '16 at 14:08
  • $\begingroup$ There is however still the issue that giving MyType[var] will pass the test, even though var may not pass the MyTypeQ test. $\endgroup$ – user31159 Sep 12 '16 at 14:10
  • $\begingroup$ @Xavier yes but GIGO :) $\endgroup$ – Kuba Sep 12 '16 at 14:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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