Assigning Sequence to Part of a SparseArray (bug?)

Part

▪ You can make an assignment like t[[spec]]=value to modify any part or sequence of parts in an expression.

▪ If expr is a SparseArray object, expr[[...]] gives the parts in the corresponding ordinary array.

SparseArray

▪ List and matrix operations are typically set up to work as they do on Normal[SparseArray[...]].

Part extracts specified parts of the array represented by a SparseArray object, rather than parts of the SparseArray expression itself.

array = Range[10]
(* {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} *)

array[[2]] = Sequence[0, 0, 0];

array
(* {1, 0, 0, 0, 3, 4, 5, 6, 7, 8, 9, 10} *)

sparse = SparseArray[Range[10]];

sparse[[2]] = Sequence[0, 0, 0];

sparse[[2]]
(* {10} *)

ArrayRules[sparse]
(* ArrayRules::rect: Nonrectangular array encountered. >> *)
(* ArrayRules[SparseArray[Automatic, {10}, 0,
{1, {{0, 10}, {{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}}},
{1, 0, 0, 0, 3, 4, 5, 6, 7, 8, 9, 10}}]] *)


Is it a bug?

-
After reading this answer I think that maybe the former case with Lists works only by a happy accident. –  Vladimir Reshetnikov Jun 25 '13 at 3:46
You might find this discussion interesting, as well as the other one linked to there in my post there. –  Leonid Shifrin Jun 25 '13 at 8:41

Analysis

It indeed looks like a borderline bug to me. Let us see what is happening. The first observation here is that part assignment does its job all right:

sparse = SparseArray[Range[10]];
sparse[[2]] = Sequence[0, 0, 0];


so that

?sparse

Globalsparse
sparse=SparseArray[Automatic,
{10},0,{1,{{0,10},{{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}}},
{1,Sequence[0,0,0],3,4,5,6,7,8,9,10}}
]


you can see that the actual rule for sparse was updated correctly.

The next observation is that the main culprit is the SparseArray object, which evaluates, and in particular the Sequence inside the list of elements gets spliced, as it should according to the main evaluation procedure:

sparse

(*
SparseArray[
Automatic,{10},0,{1,{{0,10},{{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}}},
{1,0,0,0,3,4,5,6,7,8,9,10}}
]
*)


while this is consistent with the evaluation sequence, this is where it goes wrong. To avoid this, either SparseArray should be HoldAll:

SetAttributes[SparseArray, HoldAll]


and then it works:

ArrayRules[sparse]

(*
{{1}->1,{2}->Sequence[0,0,0],{3}->3,{4}->4,{5}->5,{6}->6,
{7}->7,{8}->8,{9}->9,{10}->10,{_}->0}
*)


or, the elements inside of SparseArray should be held in some holding wrapper (Hold etc), rather than List.

ClearAttributes[SparseArray, HoldAll]


The second option would require significant changes in the SparseArray representation, so it is unlikely that such a change can be considered. I can't tell off hand whether making SparseArray HoldAll can break anything.

Workarounds

You can create a dynamic environment to do that locally:

ClearAll[withHoldingSparseArray];
SetAttributes[withHoldingSparseArray,HoldAll];
withHoldingSparseArray[code_]:=
InternalInheritedBlock[{SparseArray},
SetAttributes[SparseArray,HoldAll];
code
];


so that

withHoldingSparseArray[ArrayRules[sparse]]

(*
{{1}->1,{2}->Sequence[0,0,0],{3}->3,{4}->4,{5}->5,{6}->6,
{7}->7,{8}->8,{9}->9,{10}->10,{_}->0}
*)


As another alternative, you can create a dynamic environment where you make Sequence inert:

ClearAll[withInertSequence];
SetAttributes[withInertSequence,HoldAll];
withInertSequence[code_]:=
Module[{result,sequence},
Block[{Sequence=sequence},
result=code
];
sequence=Sequence;
result
]


and this will also give the right result for your example:

withInertSequence[ArrayRules[sparse]]

(*
{{1}->1,{2}->Sequence[0,0,0],{3}->3,{4}->4,{5}->5,{6}->6,
{7}->7,{8}->8,{9}->9,{10}->10,{_}->0}
*)


This method can however be more dangerous / prone to errors than the first one.

Conclusions

So, on one hand, SparseArray object evaluates precisely according to the standard evaluation semantics. On the other hand, this leads to behavior which one can probably classify as a bug. I'll file a report on this.

-