Skip to main content

What Is Programming?

·2773 words·14 mins

Introduction

I have been a lecturer in computer and data science for five years now, working at a university of applied sciences in The Netherlands. These universities offer the highest level of vocational education. As of last year I am also a researcher associated with the center of expertise for technical innovation1 where I study the developments in programming language theory and how these can contribute to the education of future software developers.

While I enjoy teaching advanced courses in the later years, I find teaching programming to first year students to be very rewarding. Over the years and through conversations with students, my past class mates and colleagues, I have developed a series of ideas on what constitutes a good introductory programming course.

In the coming blog posts, I will share my opinions, not in the last place, in the hope that it will provide me with some helpful and constructive comments and suggestions. Teaching involves a decent amount of reflection and intervision is key in this regard. Via this route, I might be able to benefit from the collective experience of colleagues worldwide.

The three acts of software development.

Recently, I have been thinking about what the act of programming essentially is. Surely, complex systems such as operating systems, web browsers and CRM-systems have to be programmed, but is that the essence of programming? What does that activity have in common with the much more mundane task of automating calculations on a programmable calculator? How do they differ?

Let us first entertrain the idea that these undertakings are on opposites ends of some spectrum and ask what sets them apart.

  • Typical calculator programs tend to be short sequences of instructions executed from top to bottom with the occasional branch or loop. Higher degrees of abstraction are usually absent. On the other side are behemoths of applications - browsers, automated trading systems, et cetera - that consist of a multitude of interoperating components in each of which we find functions and classes in profusion. In short, we notice a difference in complexity2.

  • The calculator program most likely is an ad hoc solution to a well-defined problem. It fulfills a specific and temporary need of its creator. We would likely evaluate the quality of the code in terms of its immediate adequacy to the problem at hand. We do not assess the code by its maintainability, documentation or elegance. If it gets the job done, we will take it.

    However, this cavalier approach will only get us so far. Larger pieces of software written by hundreds, nay thousands of programmers that work on separate components require a much more disciplined approach and a careful design up front. When people or businesses come to rely on the software for some of their core activities, ensuring the longevity of it becomes paramount.

It is improper to state that these are the only dimensions on which to measure the two endeavors, however, enumerating an exhaustive list is not the goal here. Let us now direct our investigation at what the commonalities are.

  • All programs are eventually expressed in some, tough not necessarily the same, programming language. But expressions are a representation of the author’s conceptual model which is based on an (in)direct analysis of a particular universe. The classical philosophers knew about this threefold correspondence, which is presented in Granström3 in a diagram similar to the one shown in the figure below.

    Figure 1: The threefold correspondence between objects from a universe, the mental model (concepts) and the resulting expression given in the form proposed by Granström (2011).

    Although there is an interplay between the command of a language and what is conceivable, there exist many languages in which to express the same concept. These need not be formal or programming languages. Many concepts may also, to varying degrees of ambiguity, be expressed in a natural language, or a visual language such as UML.

  • Composition is another commonality. In his treatise of applied category for programmers, Bartosz Milewski makes the case that composition is the essence of programming4.

    At the most basic level, programming is about telling the computer what to do. “Take the contents of memory address x and add it to the contents of the register EAX.” But even when we program in assembly, the instructions we give the computer are an expression of something more meaningful. We are solving a non-trivial problem (if it were trivial, we wouldn’t need the help of the computer). And how do we solve problems? We decompose bigger problems into smaller problems. If the smaller problems are still too big, we decompose them further, and so on. Finally, we write code that solves all the small problems. And then comes the essence of programming: we compose those pieces of code to create solutions to larger problems

This brief comparison of these two extremes illustrates the difference between the acts of software engineering, programming and coding. While these terms tend to be used interchangeably, we will use them differently: the terms, in this paragraph, are listed in descending order of scope in both time (longevity) and space (complexity). This is illustrated in Figure 2.

Figure 2: Activities involved in the creation of software from the largest scope (Software Engineering) to the smallest scope (Coding).

While the figure may suggest a hard demarcation between the three acts, the circles are meant to show the relation between super and subsets. That is, software engineering subsumes programming which, in turn, includes the act of coding. There is also an appreciable influence between the three. Take, for example, the object-oriented facilities introduced by the Simula programming languages were developed to address software engineering needs at the Norwegian Computing Center5. Simula then went on to influence other programming languages and software engineering practice 6.

Let us now find suitable definitions for each of these labels.

  • The term coding, as used here, was derived from the noun code which according to the Oxford English Dictionary is: “a system of words, letters, figures, or symbols used to represent others.” In other words, coding is a translation process. In the threefold correspondence it is the path from concept to expression.

  • Next, is the term programming. Although we just referred to Milewski’s definition, we choose to deviate slightly from his by also including the prerequisite action of determining what the computer should do.

    In this light programming is a creative art. It is a process that consists of, at least, three steps. Because in order to construct a program the programmer needs to

    1. understand the problem;
    2. conceive of a method to solve the problem;
    3. and then translate that solution into a working program.

    And although this enumeration may suggest a linear process, it is best thought of an iterative or circular process which includes the steps of testing and debugging until the program behaves correctly.

  • Finally, we arrive at software engineering for which we will use the definition from Titus Winters’ talk at CppCon 20177 that ran “[Software] engineering is programming integrated over time.” Winters continued:

    […] A programming task is “Oh hey, I got this thing to work.” And congratulations, programming is hard. And a lot of the time, the right answer to a problem is, I’m going to go write a program, solve that problem and be done. Right, that’s great, no judgment, totally fine. Engineering on the other hand, is what happens when things need to live longer and the influence of time starts creeping in.

    Software engineering also deals with matters conditional and incidental to building large and long-lived software. This includes architecting and managing the complexity of software, gathering and validating requirements, project management, testing, version control, release management and so on.

Let us now use the analysis from this paragraph and reflect on its implications for introductory programming courses.

Teach programming

The title of this section may seem to state the obvious. However, we should keep in mind that the students arriving at university may have never programmed before. Sure, they might be experienced computer users but that does not entail they know how computers or computer programmes work. Computers have not shipped with BASIC in their ROM’s for decades. Instead they boot into a complex operating system that abstracts away any implementation details.

If, with that in mind, we take another look at the threefold correspondence and the process of programming - understanding, solving and coding - we see what the scope of our course should be.

Should we choose to focus solely on the third activity then the student should be taught how to translate solutions of already solved problems into a computer program. The student’s task will be that of a translator or interpreter, picking from a limited but previously explained grammar and vocabulary. In other words, the course will be one on a particular programming language.

An Analogy

Courses on programming languages are of little use to the novice programmer, as the example in this paragraph will demonstrate.

Say a student wishes to learn a new natural language. If that language is not too different from the student’s mother tongue, a treatment of grammar and vocabulary should suffice. But if the languages are very different, such as when they originate from different proto-languages, this approach will not work.

For instance, should we give a native English student the task of translating a sentence such as “I don’t like fish” into Japanese, he will struggle as the Japanese will use an adjective rather than a verb to express the concept of “to like” and that the negation is achieved by conjugating the adjective. And even then, the sentence has many translations depending on context, the sex of the speaker and the relation between speaker and listener. In short, if we want our speaker to construct an appropriate sentence, the speaker must know how to build an adequate conceptual model that is translatable into Japanese and hence quite different to the English model.

A possible Japanese translation 「魚が好きじゃない。」 consists of the following

  1. the noun for fish (魚),
  2. the particle used for indicating (が),
  3. and the adjective for like (好き) conjugated to be negative.

Note the absence of both verb and subject. What is not obvious from this sentence is that it should not be used when addressing a superior.

Back to programming languages

Let us look at one more example before we move on. In programming we also find what we can think of as proto-languages. The most notable examples are Turing machines and Church’s lambda calculus. These are at the heart of imperative and functional programming languages respectively. Although these families of languages are very different, the Church-Turing thesis states that both can be used to solve any computable problem.

Now, the dominant languages taught at vocational universities, such as Java, C# and Python, are based on the family of Turing machines. Transitioning between languages within this family is easy; a conceptual solution that works for one language, will also do for the others. For instance, imperative languages share the notion of a while-loop. Such a, seemingly basic, facility is not offered by languages based on Church’s lambda calculus. Instead these rely on recursion and deconstruction of values. Solutions formulated in these languages bare little to no resemblance to their imperative cousins.

Consider a problem that calculates the sum of a sequence of integers. A typical solution in Java will resemble the following:

public static int sum(int[] array) {
  int result = 0;
  for(int i : array) {
    result += i;
  }
  return result;
}

A, naive, equivalent in Haskell, is:

sum' :: [Int] -> Int
sum' [] = 0
sum' (x:xs) = x + sum xs

This should illustrate that if we wish to teach an imperative programmer to write a functional program, we cannot just teach the rules of the language. We need to teach a way of thinking. And the same goes for a novice programmer, we cannot assume that our way of thinking about problems is “the natural way”. It is merely a way.

So, by now, we should realize that an introductory course must cover the three fundamental steps of the creative process that is programming. That means focusing a sizable chunk (the majority?) of the course material on the first two aspects: understanding and solving problems such that the solutions can be implemented on a computer. Rather than telling a student how she can add repetition to her program, we must first explain here how to recognize the need for repetition, different types of repetition and how she should select between them. We might think this is self-evident, but, as the imperative and functional programs show, it appears so to us only through our experience.

What students will have to learn

What, now, should a student learn in order to successfully solve problems using a computer? The last paragraph suggests we first must decide whether to teach an imperative or functional style. As object-oriented imperative programming languages are the de facto standard in industry and academia, it will have our exclusive attention for the remainder of this article.

In order to successfully solve problems in this family of languages, the student will need to learn

  1. the sequential nature of a computer program,
  2. the role of statements and expressions and how these are combined to form programs,
  3. when and how to create branches in programs,
  4. when and how to incorporate repetition into programs,
  5. know when and how to abstract and make modular solutions through what we programmers think of as functions as well as classes.

Let us now consider the task of constructing branches in imperative programming languages. When we discuss them, we tend to formulate the conditions as questions such as “Is Gerald twenty-one years old?”. In reality, computers check the truth value of propositions, “Gerald is twenty-one years old.” and as a result cannot tackle situations that require more open questions (“How old is Gerald?”)

Secondly, we will have a brief look at the peculiar nature of functions in programming languages. Most students will have had prior exposure to mathematics and thus have built some intuition about functions. However the functions they are about to see, may, on the surface, seem familiar, when, in fact, they behave very differently. For a start, many of them are partial whereas in high school mathematics they are almost exclusively total (with some notable exceptions such as logarithms and square roots). And then there are the side effects. Where mathematical functions are maps from a domain to a co-domain, these new functions can perform arbitrary I/O-operations or otherwise affect some form of global state.

These two examples are further arguments against the idea that programming involves a natural way of thinking.

Conclusion

Before designing an introductory programming course, we must first understand what the act of programming entails. We have seen that programming is an essential part of software development but we must be careful not to conflate the task, in so far as possible, with other activities belonging to the much larger realm of software engineering.

Programming is about solving problems with a computer. This means students should develop the skills necessary to take a problem, translate it into a mental model that also encompasses the solution and finally translate the solution into an expression in a programming language.

The choice of a programming language influences the nature of the mental model and the transformations therefrom and to. One can therefore not expect a student without previous exposure to similar programming languages to have this model and hence sufficient attention must be paid to its development.


  1. See the website of our research group (in Dutch).↩︎

  2. Here, we use “complexity” in the sense of “being complex” according to the following definition given by the Oxford Dictionary of English: “consisting of many different and connected parts”. The same article also includes the related but different definition, namely “not easy to analyse or understand; complicated or inricate.” which is well explained in Hickey, Rich. Simple Made Easy, 2012.↩︎

  3. Granström, Johan Georg. Treatise on Intuitionistic Type Theory. Dordrecht: Springer Netherlands, 2011.↩︎

  4. Milewski, Bartosz. ‘Category: The Essence of Composition’. Bartosz Milewski’s Programming Cafe (blog), 4 November 2014.↩︎

  5. See the archived version of Dahl, Ole-Jan, and Nygaard, Kristen. ‘How Object-Oriented Programming Started’.↩︎

  6. See Dahl, Ole-Johan. ‘The Birth of Object Orientation: The Simula Languages’. In From Object-Orientation to Formal Methods, edited by Olaf Owe, Stein Krogdahl, and Tom Lyche, 2635:15–25. Lecture Notes in Computer Science. Berlin, Heidelberg: Springer Berlin Heidelberg, 2004. DOI: 10.1007/978-3-540-39993-3_3. The authors modestly suggest that the principles pioneered by Simula would have emerged regardless of their contributions.↩︎

  7. Winters, Titus. CppCon 2017: “C++ as a ‘Live at Head’ Language”, 2017.↩︎