The behaviour we see here is due to Set
performing limited evaluation of its left-hand-side prior to the assignment.
Set
Evaluates the Left-Hand-Side
Despite the fact that Set
has the attribute HoldFirst
, it performs a special kind of evaluation upon its left-hand-side prior to performing the assignment. Specifically, if the left-hand-side has a head and parts, then that head and those parts are evaluated -- but no further rules are applied to the result.
Here is a generic example illustrating this behaviour:
ClearAll[f1, f2, a1, a2]
f1 = f2;
f2[___] := Print@"f2 evaluated!"
a1 = 1; a2 = 2;
f1[a1 + a2] = 999;
??f2

Observe how even though the Set
expression was assigning to f1[a1 + a2]
, the resulting definition is actually attached to f2[3]
. f1
was evaluated to f2
and a1 + a2
was evaluated to 3
. But also note that the pre-existing definition that matches f2[3]
was not invoked, and nothing was printed.
In contrast, if the left-hand-side has no parts, it is not evaluated:
ClearAll[a, b]
a = b;
a = 3;
??a

??b

The Case At Hand
Let us now apply this knowledge to the case at hand.
For (a @@ index) = 5
, Set
performs the limited evaluation upon the left-hand-side. index
is replaced with {3, 4}
to produce a @@ {3, 4}
which is to say Apply[a, {3, 4}]
. As noted above, the normal definition of Apply
is not expanded so this is the final left-hand-side. Next, Set
attempts to assign 5
to this expression. Since Apply
is protected, the attempt fails with an error message.
Now, a[Sequence @@ index] = 5
. By construction, the head a
is inert so it evaluates to itself. Sequence @@ index
is evaluated to produce Sequence[3, 4]
. Since a
does not have the SequenceHold
attribute, the Sequence
head is stripped yielding a final left-hand-side of a[3, 4]
. This is a valid and unprotected assignment target, so the assignment succeeds.
Why Does Set
Behave Like This?
It is necessary for Set
to perform at least some evaluation of the left-hand-side for otherwise expressions like x[i, j-1] = y
would be far less useful. We want to have the indices i
and j-1
evaluated. On the other hand, full evaluation of the left-hand-side would mean that we could never reassign a variable after its initial assignment because it would disappear from the left-hand-side by evaluation. Thus, we have these arcane rules of partial evaluation. Most of the time, these rules just "do the right thing". But occasionally, like for the present question, the subtleties of the rules are observable.
Workaround
One way to work-around the present problem is as follows:
(a[##] = 5) &@@ index
a @@ index
(* 5 *)
(a[##] = 6) &@@ index
a @@ index
(* 6 *)
The technique works even if a prior assignment has been performed.
Set
holds its first argument, so you need to force the evaluation insideSet
. To work in all cases (including the case whena
has been already defined on that index), I'd do something likeHold@@index/.Hold[inds__]:>Set[a[inds],5]
. This seems to be a duplicate of at least one similar question, but I can't find it. $\endgroup$