1
$\begingroup$

I want to define the Length property for some function that is computationally expensive and returns a list. So if the function is f, then

f[100]

might make my PC burst into flames, but I have a simple formula for what

Length[f[100]]

would be, and I want to define this. Doing

ClearAll[f];
f /: Length[f[n_]] := thelength
f[n_] := expensive

doesn't work. Length[f[x]] just gives Length[expensive] (which is 0 in this toy example).

Generally, when I define

f /: g[f[n_]] := 0

then

g[f[3]]
(* 0 *)

gives zero, as defined. But when I also define

f[n_] := n

then

f[3]
(* 3, but *)
g[f[3]]
(* is now g[3], instead of 0 *)

Doesn't this violate "UpValues before DownValues"?

$\endgroup$
2
  • $\begingroup$ This occurs because Mathematica evaluates the arguments of functions first. You need f[100] to be inert since Length has no Hold* attribute $\endgroup$
    – b3m2a1
    Commented May 3, 2022 at 1:06
  • $\begingroup$ @b3m2a1's point in the docs: "In the standard evaluation procedure, the Wolfram System first evaluates the head of an expression and then evaluates each element of the expression...The next step in the standard evaluation procedure is to use definitions that the Wolfram System knows for the expression it is evaluating." -- from The Standard Evaluation Procedure. $\endgroup$
    – Michael E2
    Commented May 3, 2022 at 1:46

1 Answer 1

1
$\begingroup$

Doesn't this violate "UpValues before DownValues"?

If I understand correctly, "UpValues before DownValues" applies when you have two definitions that match simultaneously. Using your function names, we'd need UpValues for f and DownValues for g that both apply (and the UpValues for f would apply first).

What you have is UpValues for f and DownValues for f. Those aren't in contention when evaluating g[f[3]], because f[3] will evaluate according to the DownValues and then the evaluator will move on with that result as the argument to g.

Here is a demonstration:

f /: g[arg : f[_]] := "upvalue of f: g[" <> ToString[arg] <> "]";
g[f[a]]
(* "upvalue of f: g[f[a]]" *)

Now add a definition:

g[arg_] := "downvalue of g: g[" <> ToString[arg] <> "]";
g[f[a]]
(* "upvalue of f: g[f[a]]" *)
(* demonstrating UpValues before DownValues *)

Add another definition:

f[arg_] := "downvalue of f: f[" <> ToString[arg] <> "]";
g[f[a]]
(* "downvalue of g: g[downvalue of f: f[a]]" *)

For your particular problem, maybe something like this would work:

f /: Length[f[x_]] := "cheap" /; x < 100;
f /: Length[f[x_]] := "expensive" /; x >= 100

Without more context, I'm not sure what else to suggest.

EDIT: Thought of something else

You might want a custom Length function.

f[x_] := Pause[x];
SetAttributes[MyLength, HoldAllComplete];
MyLength[f[x_]] := 
  (f[x]; "cheap f, length based on evaluating f") /; x < 10;
MyLength[f[x_]] := "expensive f, length bypassed f" /; x >= 10
$\endgroup$
2
  • $\begingroup$ Ah, thank you, that makes sense! In this case, I'll have to modify Length itself to make it work. So Unprotect[Length]; Length[f[n_Integer]] = thelength; Protect[Length]; works for me $\endgroup$
    – JEM_Mosig
    Commented May 3, 2022 at 7:09
  • $\begingroup$ Actually, that doesn't work either... $\endgroup$
    – JEM_Mosig
    Commented May 3, 2022 at 12:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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