7
$\begingroup$

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_].

TagSetDelayed::tagnf: Tag returnsList not found in Length[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?

$\endgroup$
0

3 Answers 3

5
$\begingroup$

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 *)
$\endgroup$
8
  • $\begingroup$ What do you think about using a more restrictive pattern, e.g., returnsList[a_Integer] := ConstantArray[4, a]? $\endgroup$
    – sebhofer
    Nov 25, 2015 at 14:45
  • $\begingroup$ Nevermind, I just realised this has the same flaw as m_goldberg's one! $\endgroup$
    – sebhofer
    Nov 25, 2015 at 14:51
  • $\begingroup$ @sebhofer I decided to be more general. Please check my small update. $\endgroup$
    – ybeltukov
    Nov 25, 2015 at 14:52
  • $\begingroup$ 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! $\endgroup$
    – sebhofer
    Nov 25, 2015 at 15:01
  • $\begingroup$ @sebhofer Please check the output of Quantity; Information@Quantity; ClearAttributes[Quantity, ReadProtected]; Information@Quantity as an example :) $\endgroup$
    – ybeltukov
    Nov 25, 2015 at 15:24
2
$\begingroup$

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

$\endgroup$
2
  • 3
    $\begingroup$ Unfortunately, it evaluates ConstantArray inside. $\endgroup$
    – ybeltukov
    Nov 25, 2015 at 14:12
  • 4
    $\begingroup$ @ybeltukov maybe your comment would be clearer with Length[returnsList[6]] // Trace :) $\endgroup$
    – Kuba
    Nov 25, 2015 at 14:46
0
$\begingroup$

Ah, quoting. How about:

returnsList /: Length[Unevaluated[returnsList[a_]]] := returnsLength[a]
$\endgroup$
2
  • 1
    $\begingroup$ Length[returnsList[5]] // Trace $\endgroup$
    – Kuba
    Nov 25, 2015 at 15:15
  • $\begingroup$ As with @m_goldberg's answer, this avoids the messages but doesn't actually make use of returnsLength. :( $\endgroup$
    – ZachB
    Nov 25, 2015 at 18:35

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.