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$ 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$ 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$ Commented May 3, 2022 at 7:09
  • $\begingroup$ Actually, that doesn't work either... $\endgroup$ Commented May 3, 2022 at 12:03

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.