Operations, functions and special forms in imperative programming languages

Operations, functions and special forms in imperative programming languages

In this article, we will clarify a rather subtle semantic issue that is often left behind when it comes to programming in imperative languages.

If the reader knows one of the languages ​​of the Lisp family (Common Lisp, Scheme, Clojure, etc.), especially if he has read SICP, then he is asked a question about the novelty, and can skip this article. If the reader uses Haskell or another language based on the model of lazy computing, then it is somewhat different, and the direct presentation of the material in this article does not apply to such languages, although the fundamental principles are the same.

If you’re an experienced programmer in imperative languages, then of course you already know the facts behind the material being taught, but you might be interested in streamlining the terminology.

Operations

Most imperative programming languages ​​use expression syntax close to mathematical formulas, where, in addition to actual function calls, operators with a special syntax are used, usually single-digit prefixes or double-digit infixes, for example, ~x, *p or 5+2. Without any loss of generality, these operations could be (and in Lisp are done) represented in the usual functional form of the call, for example, not(x), target(p) or plus(5,2). Therefore, when we talk about operations, we can effectively talk about functions instead, or at least something syntactically similar to functions, with the same success. Next, we can specifically not allocate operations among other functional objects.

Functions

In fact, all imperative programming is built on the application of functions (including operations). In object-oriented programming, the functions included in the class are called methods, but they are the functions themselves.

For us, the parameter transfer mechanism is now important in the function application mechanism.

The vast majority of modern imperative programming languages ​​ultimately use two ways of passing parameters – by value and by reference. (Nature and human thought are not limited by these methods, and, for example, the Algol language used a method of passing parameters to a name that falls out of our reasoning, but later decided that the same results can be achieved much more beautifully by passing by the value of some lambda-expression) .

Passing parameters by both value and reference means that the value of the actual parameter is first calculated, and then that value itself or a reference to it is passed inside the function for use as a formal parameter. (Not all languages ​​that support passing reference parameters support using an expression for a reference, but that’s not important to us).

For the strictness of the presentation, we note that the function that is called itself is also essentially a parameter of its call, which is used by value – for example, a specific function can be calculated by referring to an array of functions. (The semantic equality of the called function and its parameters is fully reflected in the syntax and semantics of the Scheme language).

Thus, the method of calling a function is as follows: we calculate the called function, calculate the values ​​of all its actual parameters, call the function with parameters. In most imperative languages, the exact order of calculating parameters and functions is not defined.

Example:

(*func) (x+3, ++i);

Here we calculate the triplet of the values ​​of the expressions *func, x+3, ++i (since this is the C language, the calculations do not necessarily take place in this order) and after that we call the function indicated by func with the calculated values ​​x+ 3 and ++i.

x+y

Here we are defined by the value of the + operation, calculate the x value, calculate the y value, and then apply the + operation to the calculated x and y values.

Special forms

It is customary to call a special form a construction that syntactically looks like a function call (including an operation), but the semantics of calculating its parameters differs from that established for functions.

Unfortunately, in practice, very rarely when teaching imperative programming languages, attention is paid to the differences between functions and special forms, although due attention is usually paid to the method of calculating actual parameters when considering functional languages.

In most imperative languages, three special forms are implemented: logical AND, logical OR and conditional operation.

In operation x && y the value of parameter y is calculated only if the value of parameter x is valid.

In operation x || y the value of the parameter y is calculated only if the value of the parameter x is false.

In operation x ? y : z only one of the parameters y or z is calculated depending on the truth of the parameter x.

What happens if we try to replace the && operation in C with our own And function?

bool And (bool x, bool y) {
  return x&&y;
}

Such a replacement is not semantically equivalent. For example, we have the right to write:

if ((i >= 0) && (array[i] > 0)) ...

but we do not have the right to replace such a construction with:

if (And (i >= 0, array[i] > 0)) ...

In the first case, if i < 0, the access to the array will not occur, because the second parameter of the & operation is calculated only if the first one is true.

In the second case, if i < 0, then the second parameter of the And function will still be calculated based on the general order of the function call, and the array will be exceeded during this calculation.

In most programming languages, logical AND and logical OR operations are not logical functions in the strict sense, but are special forms. They do not operate on a truth table, although their output matches the corresponding functions when applicable.

For the same reason, functions like coalesce are a complete replacement for the conditional operation, since there are special forms.

Usually, a new, special form invented by a programmer, unlike a function, cannot be added to a program by means of language.

Related posts