Tips

Looking at the Expressive Power of Different Programming Languages Through an Interview Question

Many people do not understand that different programming languages have different expressive power. That is why, after assembly language, we still needed highe…

Published

Many people do not understand that different programming languages have different expressive power. That is why, after assembly language, we still needed higher-level programming languages. Now that software systems are becoming increasingly large, the insufficient expressive power of C and C++ is becoming more and more apparent. We should choose high-level programming languages to complete our tasks whenever possible, and use profiling where performance is needed, rather than choosing languages such as C/C++ from the outset.

Recently, I suddenly came across a very eye-catching question, and it seems that ThoughtWorks has also used something similar. The problem is very simple, but no matter how I wrote it, it felt awkward, until one day I tried writing it in DrRacket and suddenly understood. In languages without pattern matching, this problem is awkward no matter how you write it; there is no way around it. Below, let us compare implementations in two old languages, C and Scheme.

Problem description (roughly):

Traverse all positive integers in the closed interval from 0 to 100 in order. If the number is divisible by 3, output the number with the ‘*’ marker; if the number is divisible by 5, output the number with the ‘#’ marker; if the number is divisible by both 3 and 5, output the number with the ‘*#’ marker.

First, let us look at a conventional C implementation:

cpp
for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0) {
        if (i % 5 == 0) {
            printf("%d*#", i);
        } else {
            printf("%d*", i);
        }
    } else {
        if (i % 5 == 0) {
            printf("%d#", i);
        } else {
            ; // Do nothing
        }
    }
}

I have omitted parts that are not very relevant, such as the main function. I chose the two-level if-else approach with the clearest logic. Compared with other “clever” methods, it has the highest readability, the clearest logic, the least amount of computation, and is also the easiest for adding or modifying program logic.

Now let us look at the Scheme implementation:

scheme
#lang racket
(require math/number-theory)
(require racket/match)

(define (range-closed from to [step 1])
  (range from (+ 1 to) step))

(let ([numbers (range-closed 1 100)])
  (for ([x numbers])
    (match `(,(divides? 3 x) . ,(divides? 5 x))
      ['(#t . #t) (printf "~A*#" x)]
      ['(#t . #f) (printf "~A*" x)]
      ['(#f . #t) (printf "~A#" x)]
      [else (void)])))

After comparing them, we find that because Scheme has pattern matching (provided by a library), the program logic is significantly clearer than in C. To summarize the advantages of the Scheme implementation carefully:

  1. It has a function with clear semantics for generating all numbers in a closed interval: range-closed;
  2. There is no need to manually determine the left and right boundaries of the closed interval: the for loop;
  3. It has a function with clear semantics for expressing divisibility: divides?;
  4. It has a consistent naming convention for functions that return Boolean values: the ? suffix;
  5. Through pattern matching, it handles all cases of divisibility by 3 and 5 and the corresponding action for each case: '(#t, #t) '(#t, #f) '(#f, #t) else.

I have not even mentioned that in Scheme, functions can be defined inside functions, while C cannot do this (GNU extensions can); other types in C can be used directly as Boolean values, which led to the invention of twisted expressions such as NULL == ptr (but I suppose nobody here wants to use if (i % 3), right?).

This is only a very simple exercise, yet it exposes many weaknesses of C/C++. I am not saying that C/C++ has no advantages at all, but in the vast majority of cases, we should choose other languages with stronger expressive power.

Cherish life; stay away from low-level languages!