In my opinion, your question is formed on the basis of working with a small number of programming languages and extrapolating conclusions that may not be valid.
Here are some assumptions I have recognised that may not be valid which I will later explore in greater detail:
- All function calls must use parenthesis.
- All programming languages use these syntactic notations to mean the same thing
- There is some common underlying nature that requires things to be like this
I further understand you hinted that you have no significant background in Computer Science theory, but some of the background material to why functions are usually written like this, is about the theoretical evolutionary route that most current languages came from;
So, lets review some of that, as it may help readers (such as students of Computer Science and programming per se) understand your question and our answers more fully.
Consider an early computer language, such as Fortran, which was devised in 1954. It had functions and used parentheses. Actually, at that time the character set available on computer was so limited that it had no other bracketing symbols, and only had upper case letters. It did not have access to []{}<>
symbols. Originally all comparisons were made to zero so that comparison operators were not needed, but when they were later introduced they had to be written as .GT. and .LT. due to the lack of characters. The result of this was we could write:
FUNCTION B
B=1.0
RETURN
FUNCTION C(D)
C = D
RETURN
DIMENSION A(10)
A(1) = C(1) + B
STOP
END
You can see that we have used parentheses for A(1)
(which is an array) and C(1)
which is a function call, but not for + B
which is also a function call. We have used parentheses for A(10)
which is an array declaration and also for C(D)
which is a function declaration with a formal parameter. Thee design on the language is such that the compiler can work out what is intended and instruct the computer in its machine code to take appropriate actions. However, sometimes this is not convenient for the compiler writer as that may require multiple passes (or readings) of the program to determine what use of parenthesis was intended before determining the semantics of that fragment of code.
Algol 60 also has a similar situation, shown in this example:
integer function one;
begin
one := 1
end;
integer function two (integer p);
begin
two := p
end;
begin
integer array a [ 1: 10 ];
a[one] := two(1)
end
In the above example one
is a function that uses no parentheses in its declaration or invocation, whereas two
is a function that used parentheses to indicate the formal parameter list (integer p) and also the actual argument (1)
. this time they are not confused with arrays at all, because the array exclusively used []
in both its declaration and application. So thus invalidating your assumption on what ()
might mean.
We also get some symbol overloading in more modern advanced languages. Take Algol 68 for example. It also uses parentheses for arrays and functions (but also almost everything else), but also like Algol 60 that proceeded it, it uses square brackets []
:
( [] int a := (1,2,3);
print((a[1])) )
Here '[]' are used to indicate the array declaration, whereas (1,2,3)
indicates the array constant used to initialise it. The print statement is even more complex. The inner parentheses (a[1])
indicate an array constant containing one element and the outer print()
indicates a function call, and the [1]
indicates an array selection operation. The all-encompassing ()
are used for the program begin and end.
One more historical language is important here, which is LISP. Using LISP we can explore functions as first class objects, lambda functions and other aspects of functions and the notation associated with them. In LISP we can store a function in a named variable like this:
(setq double (function (lambda (x) (+ x x)) ))
Notice that everything is using parentheses; that is the nature of LISP. LISP is constructed entirely of lists denoted by the tuples in parentheses like this:
(x x x x)
Those lists can be either or both data and functions, it makes no difference. Now that the name double
contains a function we can do various things with it.
We can invoke the function like this:
(funcall double 11)
This leads into coverage of the notation used in command command languages such as Windows/DOS command prompt and unix shells. In them each line is essentially a function call:
cd path/file
Function calls can be nested by using backtick notation:
cd `which file`
Here no parentheses have been used at all, yet it is a notation familiar to many experienced computer users.
I have not really covered other uses of functions and their notations, such as functions as first class objects, passing functions as arguments, partial parameterisation, lazy evaluation and many many more concepts that could shine a light onto why function notation is as it is. We have to have a notation to ultimately say: This name is a function and I want it to be invoked at this point.
Some of what we are exploring here is the separation between programming language syntax and its semantics. How much of the symbols indicate structure and how much communicate meaning (semantics), and how can this be determined by automated means. An introduction (to a deeper) theoretical topic can be started by looking at Chomsky Language Hierarchy. Algol 60's nice separation of []
from ()
meant that a Context Free Grammar could be used to describe the syntax of the language. Algol 68, in contrast, by the use of symbol overloading (and for other reasons) mean that a more complex Context Sensitive Grammar (known as W-grammars) needed to be used.
The more complex the grammar needed to express the language the more complex the compiler implementation. Language designers wanted both a powerful and expressive language, but also one that could be implemented without undue complexity in the compiler. This often resulted in some stereotyping of language features in ways that were know to be implemented more easily.
In the evolution of languages they developed into different families as they grew from one to another. To prevent the overloading of ()
meaning begin and end the use of the symbols {}
became common place, and the use of []
for array was considered sensible. However many languages use ()
to indicate both formal parameters and actual arguments, but this is by no means a universal characteristic, and there are exceptions to everything.
When we think that many languages seem to use the name()
notation for function invocation, what we are really seeing is that many languages in common use today inherit a set of notations from the C family of languages through the Algol route. That is all we are seeing is the genealogy of language in the notations.
I hope by a brief tour on the history of the use of parenthesis in the evolution of computer languages and its relation to computer science theory you have gained an insight into what you are trying to understand.
And if it matters: yes I have worked on Fortran, Algol 60, Algol 68 and other esoteric language compilers