8
$\begingroup$

A lot of languages use square brackets for array indexing, and round brackets (also known as parentheses) for function calls. For example, in C:

int array[10];
void foo(int x);

int x = array[3]; // array indexing
foo(42);          // function call

However, there is almost never a case where you could use round brackets after an array or square brackets after a function, for example:

int x = array(3); // compile error
foo[42];          // compile error

Even in languages where you can overload both the array indexing and function call operators, it seems very rare to ever do that. So then it seems like this difference in syntax is unnecessary. And indeed, in some languages parentheses are used for both, like in most dialects of BASIC.

Why have square brackets historically been used for array indexing? What are the pros and cons of using a different notation than the one used for function calls?

$\endgroup$
17
  • 1
    $\begingroup$ And even from the abstract mathematical point of view, arrays are just functions with a peculiar domain, so ... Then again, the square bracket characters were available on keyboards, so why not use them for some special purpose, like we do with almost every (ASCII) symbol? After all, parens are already used for multiple purposes: grouping and function calls. (But if you really love parentheses, try LISP) $\endgroup$ Sep 23, 2023 at 14:15
  • 2
    $\begingroup$ Python allows overriding both round-brackets postfix (the __call__ method) and square-brackets postfix (the __getitem__ and __setitem__ methods). It would be very unusual for a Python class to override both without giving them different behaviour. $\endgroup$
    – kaya3
    Sep 23, 2023 at 15:46
  • 4
    $\begingroup$ "However, it seems extremely rare in languages where you can overload operators to have overloaded both the square bracket operator and the round bracket operator, and even if they do, it is even rarer that these would then do different things." - I don't understand the claim. C++ and Python both allow both overloads; overloading [] is normally used for an operation that is in some sense analogous to indexing; overloading () is normally used for an operation that is in some sense analogous to calling; and a type that provided both would ordinarily definitely not make them the same. $\endgroup$ Sep 23, 2023 at 20:00
  • 2
    $\begingroup$ You mean, there should only be one syntax simply because practical types wouldn't need to support both operations? I don't think that's a compelling reason at all. Semantics matter. As elegant as LISP is, programmers find it practical and useful to model certain types as storage and certain other types as procedural. $\endgroup$ Sep 23, 2023 at 21:02
  • 3
    $\begingroup$ "there should be only one syntax because indexing is just a special case of evaluating a function" ─ every expression is a special case of evaluating a function. This logic leads to very uniform syntaxes like +(x, y) or (+ x y), but a syntax being more uniform does not tend to make it more understandable. $\endgroup$
    – kaya3
    Sep 25, 2023 at 18:59

2 Answers 2

13
+100
$\begingroup$

ALGOL 58 used square brackets for array declarations and accesses (see e.g. here), so this goes back at least as far back as 1958 in programming language syntax.

According to this answer to a similar question on Software Engineering SE, earlier languages including FORTRAN did not use square brackets, because they were designed for use on IBM keyboards which didn't have them; but once square brackets became available it would have been a no-brainer to use them for something, since overloading the meaning of parentheses for too many different things would have made code harder to understand.

The question proposes that using function-call syntax for array accesses would be simpler. But this line of argument taken to its logical conclusion would lead to all expressions being written as function calls; why have x + y as a special syntax for adding numbers, when you can use function call syntax like add(x, y)? Well, there are some languages with such uniform syntax (particularly Lisp), but most of us like being able to more clearly tell apart different kinds of expressions.

Particularly for array accesses, having a different syntax makes sense because an array access is also an l-value, i.e. it is allowed to occur on the left side of an assignment like a[i] = x; and ALGOL 58 didn't have that many kinds of expressions, so there was plenty of syntax space to go around, and it's not obvious what else square brackets might have been used for if not array accesses.

That said, I suspect this notation predates programming language syntax. In mathematical notation it's common to use subscripts (e.g. $a_1, a_2, a_3, \ldots$) for sequence members, and indeed sometimes the postfix "square brackets" operator for array access is referred to as a "subscript". But in some media it is not possible to print actual subscripts, so an alternative would be needed. I'm not able to find an example where square brackets were used for this purpose before 1958, but I'd be surprised if there were no examples.

$\endgroup$
2
$\begingroup$

Historic usage

The use of square brackets as array indexing goes back at least to the B language (circa 1969).

Here, vector syntax is used to replace a lot of typing and constant forwarding, as B's vectors are akin to C's arrays. They are basically convenient syntax over pointer syntax. For example:

vec[10] = 2;

Is the same as:

(&vec)[10] = 2;

That still uses bracket, and without, it is syntax for

(&(vec+sizeof(vec)*10) = 2;

And sizeof(vec) is really a constant defined by compiler:

(&(vec+VEC_STRIDE*10) = 2;

From a[b] to (&(a+K*b)) or (&(a+sizeof(a)*b)), there is a lot of typing and semantic machinery to express.

Note that the last expression already has (a lot of) parenthesis, so another grouping was chosen to represent, replace and express this concise syntax.

Using parentheses as array access

To use parentheses as array indexing syntax would make parenthesis pairs a context sensitive token. Consider:

a(b)(c);

This is:

  • an array called a, indexed by b, that returns an array, then indexed by c;
  • an array called a, indexed by b, that returns an function pointer, then invoked with c;
  • an function called a, invoked with b, that returns an array, then indexed by c;
  • an function called a, invoked with b, that returns an function pointer, then invoked with by c;

That is, using the first parenthesis pair after an array as array indexing syntax.

This special casing for arrays may be orthogonal enough to make a regular parsing, but I suspect this will put a lot of pressure on semantic assignment of tokens to cause some performance impact.

This is not prohibitive, however, since other languages already have to examine parenthesis pairs in a lot of contexts.

The problem is that humans also will be impacted, as array indexing and function calling now use the same syntax, as illustrated in the above example.

$\endgroup$
2
  • $\begingroup$ I was thinking about parentheses for array indexing in this way: vec(10) = 2; vec(2) = vec(1); vec(10)(p); This would still allow arrays to have builtin members. $\endgroup$
    – G. Sliepen
    Sep 23, 2023 at 21:19
  • $\begingroup$ Is From a[b] to (&(a+sizeof(a)*n) meant to be From a[b] to (&(a+sizeof(a)*b) ? I tried editing the answer but it wouldn't allow me to make an edit of 1 character. $\endgroup$ Sep 25, 2023 at 10:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .