2
$\begingroup$
In[1] Length[Unevaluated[1 + 1 + 1 + 1]]
Out[1] 4

Q1) Can we construct a function named f, such that f and Length behaves just in the same way?

I've checked that Attributes[Length] yields {Protected}, which is not very important here.

So I defined f as f[x_]:=Length[x] or f[x__]:=Length[x] and thought that was all.

But contrary to my expectation,

In[2] f[Unevaluated[1 + 1 + 1 + 1]]
Out[2] 0

Q2) In general, how can we assure that the two function f,g (one of them may be built-in) behaves just in the same way? (Except some very very freaky, forced cases.)
From the above example, it is not enough to consider only their definitions and attributes.
There are somethiing more.

$\endgroup$
1

1 Answer 1

4
$\begingroup$

It's instructive to look at the output of TracePrint.

Length[Unevaluated[1 + 1 + 1 + 1]] // TracePrint
(* Out: *)

 Length[Unevaluated[1+1+1+1]]
  Length
 Length[1+1+1+1]
 4

Whereas

f[Unevaluated[1 + 1 + 1 + 1]] // TracePrint
(* Out: *)

 f[Unevaluated[1+1+1+1]]
  f
 f[1+1+1+1]
 Length[1+1+1+1]
  Length
  1+1+1+1
   Plus
   1
   1
   1
   1
  4
 Length[4]
 0

Let's try something similar, but different, before explaining this:

g = Length;
g[Unevaluated[1 + 1 + 1 + 1]] // TracePrint
(* Out: *)

 g[Unevaluated[1+1+1+1]]
  g
  Length
 Length[1+1+1+1]
 4

What's going on here?

It has to do with the order in which Mathematica evaluates expressions. There's a fair bit I'm leaving out here, but here are the important details for this case. Consider an arbitrary expression h[x1, x2, ...], and let's walk through the evaluation process.

  1. The head of the expression h is evaluated as an expression in its own right (just as we would any other expression; that is, it is run through this process). Let's refer to whatever expression h evaluates to (which most times, is simply h itself) as h1. (Note: arguments are not considered here. This is akin to putting h in a cell by itself and hitting Shift+Enter.)

  2. Each of the arguments x1, x2, ... is evaluated. If any of them begin with Unevaluated, we strip off the Unevaluated head, and then skip evaluation of that expression.

  3. Apply any downvalues for h1, i.e., rules that are used to transform the cases where h1 is given arguments. So, e.g., rules that are created by definitions like h1[x_] := .... Then, evaluate the resulting expression.

This process is recursive, so when we evaluate the resulting expression, we start over again at the top.

With this in mind, let's make sense of the previous evaluations.

  1. Length[Unevaluated[1 + 1 + 1 + 1]]. Length evaluates to itself; Unevaluated is stripped off; and we apply the definition (downvalue) of Length to Plus[1,1,1,1] directly, which is 4. (Note that this downvalue for Length is built-in, so you won't find it under DownValues[Length].)

  2. f[Unevaluated[1 + 1 + 1 + 1]]. f evaluates to itself (as a bare expression—we're not applying to arguments yet!); Unevaluated is stripped off; and we apply the downvalues of f to Plus[1,1,1,1]. Here, that downvalue is given by f[x_] := Length[x], so we substitute Plus[1,1,1,1] into Length to get Length[Plus[1,1,1,1]]. Now, here's the crucial bit—since this came from a downvalue, we now evaluate that expression! In the previous example (Length[Unevaluated[1 + 1 + 1 + 1]]), at this stage, we applied the (built-in) downvalue of Length to Plus[1,1,1,1] immediately to get 4. But here, we're starting the evaluation process for this expression from the beginning again (step 1); so, we evaluate Length, then evaluate Plus[1,1,1,1] to get 4, then evaluate Length[4], which is 0.

  3. g = Length; g[Unevaluated[1 + 1 + 1 + 1]]. Here, we evaluate the head (g) first. Since g, by itself (no arguments), evaluates to the symbol Length, we then proceed with the evaluation process with Length in front: strip off Unevaluated and skip evaluation of Plus[1,1,1,1], then see if there are any downvalues we can apply to the head of the expression. But, oh wait...the head has already been evaluated to Length! And there is indeed a built-in downvalue for the head Length, which gets us 4 (just as it did in (1) at this same stage).

So, the moral (and solution)?

Define the head to be Length directly!

Since the head is always evaluated first, no matter what (unless your expression is, say, being examined by some other expression, which is preventing evaluation from happening normally), you can be safe defining g = Length. This even applies for the more complicated things—upvalues, Hold* attributes (even HoldAllComplete!), and the rest. All that is only taken into consideration after the head of the expression, as an expression in its own right, is fully evaluated.

The only thing that could cause a problem is something preventing the expression (or g) from evaluating normally, e.g. Hold[g[1+1+1+1]], some symbol with a Hold* attribute, or something like Block[{g}, g[1+1+1+1]]. For this, if you want a True alias in your notebook, you can define $Pre to be a function which changes any occurrence of g to Length. $Pre is a global variable applied to any input expression before any sort of evaluation of it begins whatsoever.

Let me know if any of this is unclear, or if you have further questions!


Alternatively: Now, let's say you want something more complicated—not just substituting one function for another. Maybe one function for a list of two others.

For that, we can exploit the attribute HoldAllComplete. This intervenes immediately after the head is evaluated, and skips all evaluation of arguments (even upvalues), going directly to the step where we apply downvalues. So, for example,

ClearAll[f];
SetAttributes[f, HoldAllComplete];
f[x_] := {Length[x], Length[x]}

f[Unevaluated[1 + 1 + 1 + 1]]

(* Out: {4, 4} *)

HoldAllComplete has caused the arguments to be immediately substituted into the downvalue definition. So, by "skipping right to the downvalue step", we've replicated Length with downvalues instead of ownvalues—that is, with argument-taking definitions instead of something of the form f = Length or f := Length (which, by the way, would both work!)


Aside: There is another way to try to answer this question, which is to compare the definitions of the symbols via the undocumented function Language`ExtendedDefinition or Language`ExtendedFullDefinition. The problem is that this only helps us compare definitions for user-defined symbols, as the downvalues for Length are built-in, and so don't have Mathematica-visible downvalues. (Try it—you'll get an empty DefinitionList!) So, it's not necessarily relevant here, but I wanted to mention it as a way to inspect symbol behavior in certain contexts.

$\endgroup$
2
  • 1
    $\begingroup$ Really really thank you! I add a few things to improve reader's understanding : Read the first appearing 1. 2. 3. on and on until you get used to. The second appearing 2., 'Last time' means 'previous example', and 'from scratch' means 'from the beginning'. $\endgroup$
    – imida k
    Aug 29, 2021 at 9:53
  • $\begingroup$ @imidak nice, thanks for the suggestions, I've edited it to match! :) $\endgroup$
    – thorimur
    Aug 29, 2021 at 21:58

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.