Why does Mathematica use [[ ]] notation for array indexing?

I am confused by why Mathematica uses [[3]] to get the 3rd element, or [[i,j] to get the i,j-th element of a 2D array.

This seems counter-intuitive. Is this main reason for this to separate array-indexing from function calls?

i.e. where I see:

f[3, 4] <-- this is currently function call
f[[3, 4]] <-- f is a 2D array, and we're accessing 3,4-th element


Question:

Given that Mathematica knows the type of the arguments, can't it infer:

• if we're dealing with a function, apply arguments
• if the object is an array, index it?

Thus, why do we need separate [[ ]] notation for array indexing?

-
most programs are written to also be read by other humans, in which case the distinction between [[ ]] and [ ] is helpful. – R. M. Jul 10 '12 at 4:04

In addition to Brett's counter-example, it might be helpful to view this from Mathematica's philosophy, which is "everything is an expression". In this framework, you're not really indexing a 1D/2D array, but you're extracting a Part from an expression.

Indeed, you can use the ⟦ ⟧ notation on any expression, not just lists/matrices. For example:

Sin[x + y][[1]]
(* x + y *)

Graphics[{Red, Disk[]}][[1, 1]]
(* RGBColor[1, 0, 0] *)


The output of Part on any expression can be viewed as the argument of some function in the FullForm of the expression. For example, breaking down the second example above:

Graphics[{Red, Disk[]}][[1]]
(* {RGBColor[1, 0, 0], Disk[{0, 0}]} <-- argument of Graphics[] *)

Graphics[{Red, Disk[]}][[1, 1]]
(* RGBColor[1, 0, 0] <-- argument of List[] *)

Graphics[{Red, Disk[]}][[1, 2, 1]]
(* {0, 0} <-- argument of Disk[] *)


The 0th part is the Head of the entire expression. Since none of the above constitute as being a function call, it makes sense to use a different notation to avoid any ambiguity.

-
Phrased another way, [[]] on non-array objects is like doing a FullForm[Hold[...]] and then treating that as a nested list, which we extract from? – user1602 Jul 10 '12 at 4:19
They don't show up in chrome on win 7 either. – Ajasja Jul 10 '12 at 11:48
@Sjoerd what about these?: 〚 〛 – Mr.Wizard Jul 10 '12 at 12:22
@SjoerdC.deVries Boxes on mine too. Perhaps a reason to not use obscure unicode? – R. M. Jul 10 '12 at 13:02
@Mr.Wizard I see them correctly. – Ajasja Jul 10 '12 at 13:16

No.

For example, functions do not have to be atomic. It can be possible to extract parts from them (although it's generally not recommended.)

In[1]:= if=Interpolation[Range[10]^2]

Out[1]= InterpolatingFunction[{{1,10}},<>]

In[2]:= if[3]

Out[2]= 9

In[3]:= if[[3]]

Out[3]= {{1,2,3,4,5,6,7,8,9,10}}

-

Let me give two examples illustrating the conceptual difference between Part and [].

Here's an example showing how parts of every expression, even graphics expressions, can be accessed using Part:

g = Graphics[Circle[]];
g
g // FullForm


so g is, in fact, Graphics[Circle[List[0, 0]]], even though Mathematica displays it in the front-end as

So you can extract its parts directly: g[[1, 1, 2]] evaluates to 0, for example.

So Part is a way of accessing specific parts on an expression tree.

On the other hand, you can think of f[2] as analogous to a hashtable. Thus,

Do[f[i] = RandomReal[], {i, 3}]
DownValues[f]
(*
{HoldPattern[f[1]] :> 0.201718,
HoldPattern[f[2]] :> 0.287401,
HoldPattern[f[3]] :> 0.531829}
*)


and eg f[2] evaluates to 0.287401. Defining this kind of thing with patterns, like f[x_]:=x^2 is no different: you are merely matching a more general pattern than you are with eg f[3]. You could have arbitrary expressions inside the brackets, and this is often used to implement memoization, although it needs some work for more complicated situations.

So actually f[[3]] and f[3] are conceptually completely different operations.

-