# TagSetDelayed on a function with a delayed value

I'm trying to automatically optimize cases when the user evaluates Length[returnsList[x_]] to instead use returnsLength:

Clear[returnsList, returnsLength]
returnsList[a_] := ConstantArray[4,a]
returnsLength[a_] := a
returnsList /: Length[returnsList[a_]] := returnsLength[a]


That causes

ConstantArray::ilsmn: Single or list of non-negative machine-sized integers expected at position 2 of ConstantArray[4,a_].

I'm surprised that returnsList is evaluating. I tried SetAttributes[returnsList, HoldAll], but I get the same message (and I don't want to deal with Hold* unless I must).

The first example in ref/TagSetDelayed likewise breaks if I assign a value to g, g[x_] := 2, but this seems like the intended use case. What am I doing wrong?

Your goal is clear, but it contradicts with Mathematica evaluation order. The main problem is that Length evaluates its argument. Please check this question and especially Leonid's comments. You can use my answer with two UpValue definitions from there, but I don't recommend to do so.

Clear[returnsList, returnsLength]
Length[returnsList[a_]] ^:= returnsLength[a]
h_[x___, returnsList[a_], y___] ^:= h[x, ConstantArray[4, a], y]
returnsLength[a_] := a

x = returnsList[6]
(* {4, 4, 4, 4, 4, 4, 4, 4, 4, 4} *)

Length[returnsList[6]]
(* 6 *)

returnsList[6]
(* returnsList[6] *)


Instead, I propose to use dummy length definition with HoldAll attribute:

ClearAll[returnsList, length]
returnsList[a_] := ConstantArray[4, a]
SetAttributes[length, HoldAll];
length[returnsList[a_Integer]] := a
length[returnsList[a_List]] := First[a]
length[x_] := Length[x];

length[returnsList[6]]
(* 6 *)

length[returnsList[{2, 4}]]
(* 2 *)

length[returnsList[6]] // Trace
(* {length[returnsList[6]],6} *)


Another possibility is to build your own "object":

ClearAll[myList]
Normal@myList[a_] ^:= ConstantArray[4, a]
Length@myList[a_Integer] ^:= a
Length@myList[a_List] ^:= First@a

myList[6]
(* myList[6] *)

Normal@myList[6]
(* {4, 4, 4, 4, 4, 4} *)

Length@myList[6]
(* 6 *)

• What do you think about using a more restrictive pattern, e.g., returnsList[a_Integer] := ConstantArray[4, a]? – sebhofer Nov 25 '15 at 14:45
• Nevermind, I just realised this has the same flaw as m_goldberg's one! – sebhofer Nov 25 '15 at 14:51
• @sebhofer I decided to be more general. Please check my small update. – ybeltukov Nov 25 '15 at 14:52
• I like both your solutions. I think the first one might be more appealing to the OP as it seems more transparent. I also like the Normal-form of myList a lot. I never thought about this! – sebhofer Nov 25 '15 at 15:01
• @sebhofer Please check the output of Quantity; Information@Quantity; ClearAttributes[Quantity, ReadProtected]; Information@Quantity as an example :) – ybeltukov Nov 25 '15 at 15:24

I am not sure you are doing anything wrong (i.e., perhaps this is a bug) but a little experimenting shows that your TagSetDelayed expression must be evaluated before returnsList has any down values. That is, the following sequence of evaluations succeeds.

Clear[returnsList, returnsLength]
returnsList /: Length[returnsList[a_]] := returnsLength[a]
returnsList[a_] := ConstantArray[4, a]
returnsLength[a_] := a
Length[returnsList[6]]


6

• Unfortunately, it evaluates ConstantArray inside. – ybeltukov Nov 25 '15 at 14:12
• @ybeltukov maybe your comment would be clearer with Length[returnsList[6]] // Trace :) – Kuba Nov 25 '15 at 14:46

returnsList /: Length[Unevaluated[returnsList[a_]]] := returnsLength[a]

• Length[returnsList[5]] // Trace – Kuba Nov 25 '15 at 15:15
• As with @m_goldberg's answer, this avoids the messages but doesn't actually make use of returnsLength. :( – ZachB Nov 25 '15 at 18:35