Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.

Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit.

Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 11.2: Programming Paradigms

../_images/person07.png

Introduction

In the olden days of Week 2, it came up that there are different ways to program — different programming paradigms — and that object-oriented programming is one of them. Now that you have some experience as a programmer, it’s a good time to return to this subject.

Recognizing the main programming paradigms is part of a programmer’s general knowledge. Even a beginner should be aware of these different styles, because it’s nearly impossible to study programming from books and websites without running into mentions and discussions of them.

The word “paradigm” suggests fundamental differences in ways of thinking. Fundamental differences in thinking tend to arouse strong emotions. The matters that we’re about to discuss have prompted — and continue to prompt — highly divergent commentaries from opinionated programmers who each seek to mark and expand their territory.

In this chapter, we adopt a relatively dispassionate stance. We’ll mention several paradigms, and it is our hope that you’ll learn to regard each one without prejudice — even those that initially seem weird or even stupid. As your programming experience grows, so will your understanding of the different paradigms. One of the reasons why people keep crossing swords over these matters is that reasonable arguments can be made in support of different positions. The evidence in support of the arguments is, sadly, often purely anecdotal.

At a future time, the prevalent paradigm-based thinking may become obsolete. For now, though, paradigms are something you should know about. If for no other reason, at least because they frame how people speak and write about programming.

A Few Prominent Paradigms

Before we consider any one paradigm more closely, let’s look at the relationships between a few prominent programming paradigms. The short presentation also serves as a map for the rest of the chapter.

In this chapter, we’ll discuss only a handful of paradigms. There are others; see, for example, the relevant article on Wikipedia.

Identifying a programming paradigm

Things would be easy if we could neatly pigeonhole each program and each programming language into a single paradigm. That is emphatically not the case, however.

Paradigms overlap each other. Many are “sub-paradigms” of another paradigm; that is, they are specific forms of some broader programming tradition. For that reason alone, a single program can fall within multiple paradigms at the same time — and indeed most programs do. What’s more, a single program may combine even disparate paradigms if its different components are written in different ways.

Generally speaking, it doesn’t make too much sense to ask the yes/no question of whether a specific program belongs to a specific paradigm. It may be more fruitful to consider the extent to which a program is representative a paradigm.

Just knowing which language a program is written in is not enough to tell us which paradigm(s) the authors have operated in. For example, Scala is designed to support multiple programming paradigms and their combinations. In O1, we have written many object-oriented programs, the majority of which are imperative but some of which are largely or even purely in the functional style.

How do the paradigms differ, then? The answer is complex. Moreover, the answer is complicated by the lack of hard-and-fast definitions of each paradigm that everyone would agree on; this chapter relies on the author’s interpretation. In any case, this chapter isn’t the place to even attempt a thorough analysis of all programming paradigms. What we’ll do instead is consider two specific questions that elaborate on the above diagram. Here they are:

  1. How does object-oriented programming differ from other forms of programming?

  2. How does imperative programming differ from functional programming?

Object-Oriented Programming vs. Programming without Objects

../_images/paradigms_oop_nonoop-en.png

The yellow borders highlight the parts of the paradigm landscape that we’re going to compare next.

A few key differences

The table below captures some of the essential features of object-oriented programming.

Object-oriented programming

No objects; e.g., procedural programming

Entities in the program’s domain are modeled as objects. Operations on those entities are modeled as methods on those objects.

Operations are modeled as functions (that aren’t attached to objects; objects are either not used at all or, if they are, don’t play a major role in program design).

A program’s behavior is conceived of as communication between objects that send messages to each other (thereby calling methods). An object may delegate subtasks to other objects.

A program run happens by calling a “main function” that calls other functions, which in turn call more functions.

Each object typically stores some information that it needs while it runs its methods. objects may receive additional information as method parameters. An object may also consult another object for information.

The information that a function needs is passed in via the function’s parameters.

Try imagining what our programs might look like if, after Week 1, we bypassed the concepts of object, class, and method. Instead, we might have continued by constructing increasingly complex programs from separate functions that call other functions. Had we done that, we would have ended up doing imperative and/or functional programming without objects.

More features of OOP

The table below lists some characteristics that, while not definitive of the object-oriented paradigm, are commonly associated with it.

Object-oriented programming

No objects; e.g., procedural programming

Data types are (often) represented as classes.

No concept of class, or classes play only a bit-part role in programs.

Program design primarily seeks to identify the concepts (classes and objects) of the problem domain and their methods.

Program design seeks to identify operations (functions) and the data they operate on (parameters).

Programs and program design are often thought of as “noun-driven”, since objects and classes commonly represent things that natural language expresses as nouns.

Programs may be thought of as “verb-driven”, but there are data-driven approaches to programming without objects, too.

Is arguably a natural way for humans to model the problem domain. Conceptual modeling isn’t unique to OOP but the paradigm tends to highlight it.

Is arguably a natural way for humans to model algorithms.

Emphasizes the principle of information hiding: objects/classes each have their own areas of responsibility, and one cannot access their private parts (implementation) from the outside. Information hiding makes programs easier to understand and develop further.

Information hiding is not necessarily equally emphasized, but this crucially depends on which specific approach is chosen and on the programmer. The problems of global variables and spaghetti code are often imputed to (bad) procedural programming.

Connected information is commonly stored in the instance variables of an object.

Connected information is commonly stored in the variables of a record (tietue; an object-like construct without methods) or as a tuple.

Puts forward inheritance as a means of representing hierarchies of concepts.

No inheritance.

Common in both the academic world and the software industry.

Common in both the academic world and the software industry.

Object-orientation in O1

O1’s official goals include teaching object-oriented programming, which shows in how the course is designed. Apart from the “loose functions” of Week 1, most of our programs are representative of OOP.

Imperative Programming vs. Functional Programming

Declarative programming is a disputed and nebulous concept. We’ll gain more from comparing imperative programming to a selected subparadigm of declarative programming than from an attempted comparison of imperative programming with the entirety of declarative programming.

../_images/paradigms_imp_func-en.png

We’re about to compare the two paradigms framed in yellow.

The key difference: how to represent state?

The primary difference between imperative programming and functional programming is their approach to representing state.

In an imperative program, executing an instruction in code may change the value of a variable, pause to read user input, or cause another effect on program state that is observable in the instructing code. Executing an instruction can impact on how subsequent instructions mutate the program’s state.

In contrast, in a (pure) functional program, the program code consists of definitions that don’t have observable effects on state in this sense: there are no “side effects”. For instance, once a variable’s value is set, it never changes.

The table below elaborates on this key difference.

Imperative programming

(Pure) functional programming

It is possible and acceptable to write functions that directly modify the state information stored by the program; for instance, an object may have a method that modifies the values of its instance variables. A function’s return value may depend on — not only which parameters you pass in — but also the program’s current state.

Functions are effect-free. They don’t change the values of existing variables. Given a particular set of values as parameters, a function will always return the same value. The state of an existing object is never changed; if you want an object to represent a different state, you create a new object.

In other words, data structures such as objects may be mutable.

Data structures such as objects are always immutable.

Domains that feature state changes can be modeled in a straightforward and highly intuitive fashion (e.g., the balance of a bank account)

Domains that feature state changes can certainly be modeled; a typical approach is to represent different states explicitly. For instance, to represent the different states of a bank account after each deposition and withdrawal, you may create new objects that represent those changed states.

The programmer calls subprograms not only to obtain return values but also to cause effects on state.

For instance, myAccount.withdraw(1000) mutates the state of an account object. Even if you already know that method call will return 500, it makes a difference if your code says val received = 500 or val received = myAccount.withdraw(1000).

Each repeated invocation of a method may impact on the account object’s state, and it is significant how many times you call such a method on an object.

The programmer calls subprograms (only) to obtain their return values. Assuming you know what a subprogram returns, you don’t even need to call it. Any expression in the program can, in principle, be replaced by another expression that evaluates to the same value, without changing what the program accomplishes. (That principle is known as referential transparency.)

For instance, if the function call computeResult(10) returns 123, you could just as well write 123 in the code.

As far as program correctness is concerned, it is irrelevant how many times a particular function is invoked.

One practical difference in Scala programming is that the pure functional style uses only vals, never vars.

Functional programming is often practiced in impure forms. For example, one might write most of an application in a purely functional style, but implement the user interface in an imperative style.

Referential transparency and “effects”

As indicated in the table above, one of the key principles of functional programming is that you may substitute an expression’s value for the expression itself without changing the program’s meaning. What exactly does it take to follow this principle?

First, the principle implies that any expression must evaluate to the same value every time. And in particular: each invocation of a function on the same parameter values must invariably produce the same return value. As an example, max(a, b) follows this rule but Random.nextInt(10) doesn’t, nor does myCounter.value.

Second, evaluating an expression must not cause any effects that you can externally observe and that are meaningful in terms of what the program does. For instance, n = n + 1 observably changes the variable’s value and println("Ave") observably changes the program’s output.

But “meaningful in terms of what the program does” sounds vague. What counts as meaningful?

That’s something for the programmer to decide, and we already did something of that sort early in O1: in Chapters 1.6 and 1.7, we specified that changes to an object’s state and printing of textual output count as meaningful effects but the passing of time while a function runs doesn’t, nor does receiving a return value; this delineation is a common one. Other factors that we have chosen to ignore as effects include the allocation of memory for new objects and the computer’s consumption of electrical energy as it executes our programs. In some other contexts, it can make sense to consider those effects significant, too.

The meaningfulness of effects, then, depends on your goals and your point of view. A good rule of thumb is this: an effect is meaningful if whether it happens affects whether you can consider your program to work correctly.

For further information, take a look at online forums and the book Functional Programming in Scala (see Books and Other Resources).

Terminological note about “effects”

“Effects” in the sense we use the term here and elsewhere in O1 are often called “side effects” in other contexts. If you read other sources that discuss pure functional programming, you may find people using the word “effect” differently, in a sense that is not synonymous with “side effect”. See, e.g., “What do “effect” and “effectful” mean in functional programming?”.

Functional I/O

This chapter may have given the impression that a purely functional program can’t interact with its user.

It’s true that there are programs that are otherwise functional but do I/O imperatively. However, there are tools for functional I/O as well. As a demanding and voluntary activity, look up this subject online.

Another difference: higher-order functions

What is the role of functions in programming? What is the preferred way to repeat commands in a program? Traditionally, different paradigms have leaned towards different answers:

Imperative programming

Functional programming

Subprograms and the data they operate on are often thought of as separate things. On the one hand, there are methods/functions that implement operations; on the other hand, there is data stored as values in variables.

Functions are seen as a significant and common form of data. It is common to write and use higher-order functions: functions that take functions as parameters or that return functions. (This is part of the reason why the paradigm is called “functional”.)

The primary means of repetition is iteration with a looping construct. Loops modify program state. Effectful fordo loops (Chapter 5.5) are representative of imperative programming, as are while loops (Chapter 9.1).

Higher-order functions such as map, filter, takeWhile, and foldLeft are in common use; such functions can be used to achieve repetition without affecting program state. Alternative means of repetition include generator expressions (not covered in O1) and recursion (Chapter 12.2). while loops aren’t used, as they serve no purpose if “side effects” on program state are disallowed.

As noted above, imperative programs tend to make less use of higher-order functions, but that doesn’t mean that such functions are unique to functional programming or that imperative programs never feature them. Programming paradigms are not purely technical constructs but cultural, too: the difference is that it’s customary do things differently in different paradigms.

Imperative and functional programming in O1

O1 primarily teaches imperative object-oriented programming; many of the programs in this ebook feature mutable state. However, the course is designed to also give you a taste of functional programming, a paradigm that features more prominently still in O1’s follow-on courses. In particular, the way we’ve used higher-order methods to work on collections since Chapter 6.3 is typical of functional programming.

Examples of Imperative and Functional Programming

Let’s take a look at a few familiar programs and see if we can spot any distinctive paradigmatic features in them.

Examples of imperative programming

class Counter(var value: Int):

  def advance() =
    this.value = this.value + 1

  override def toString = "value " + this.value

end Counter

The counter class from Chapter 3.1 is an example of imperative OOP. We have a var; the advance method has a “side effect” on program state; Counter objects are mutable.

Further representatives of the imperative style include (among many others):

  • Scala’s Buffer class. The state of a buffer changes when elements are replaced, added, or removed.

  • The class o1.Grid: you can mutate a grid’s state by replacing its contents at a particular GridPos.

  • The GUI class o1.View is designed so that a View object is a view of a single mutable object that serves as the domain model and whose state is changed by the view’s event handlers.

Examples of functional programming

In Chapter 2.5, we sketched out the Pos class. Here’s some of its code:

class Pos(val x: Double, val y: Double):

  def description = "(" + this.x + "," + this.y + ")"

  def add(dx: Double, dy: Double) = Pos(this.x + dx, this.y + dy)

end Pos

Pos objects are immutable: all their variables are vals that point to immutable objects. When you compute, with add, a new Pos relative to an existing one, you don’t change the original object but create and return a new Pos.

Other examples:

  • o1.Pic objects are similarly immutable. Their methods don’t modify the original image but generate a new one.

  • The methods in class Odds (Chapter 2.5) also follow this principle.

  • A Vector is an immutable collection (Chapter 4.2).

  • The GameState class of the Peeveli game (Chapter 10.2) is immutable, too.

Another example of the functional style is the standard String class. No String object ever changes after being created; for instance, when you concatenate strings with text1 + test2, you get an entirely new, longer string and leave the originals as they were.

Example: String vs. StringBuilder

This may be a good place to mention that Scala does have an alternative representation for strings: the class StringBuilder is an “imperative version of String”. Its instances are mutable strings:

val text = StringBuilder("man")text: StringBuilder = man
text.append("bear")text: StringBuilder = manbear
text.append("pig")text: StringBuilder = manbearpig

String is usually the more convenient choice, but StringBuilder is useful when you want your program to concatenate a huge number of strings and need to optimize the program’s efficiency. With StringBuilder, the computer doesn’t have to create as many separate objects or collect that many unneeded objects as garbage.

Combining paradigms in a program

The manner in which a class is designed and the manner in which that class is used in a program are two separate things. For example, even though the design of the String type represents the functional style of programming, it’s still possible to use the class in imperative code:

var text = "cat"
text = text + "fish"    // change in a var; this code is imperative in nature

It’s also perfectly possible to write an imperative-style implementation for a class that provides a functional-style interface to its users. For example, a method in the class might be implemented using local vars, despite the class’s instances being immutable.

Mini-assignment: from imperative to functional

Especially if you feel unsure about why we just classified some code as imperative and some as functional, you may wish to do the following short assignment where you take some familiar imperative code and reimplement it in the functional style.

Task description

There’s an alternative way to write the Match class from Chapter 4.4: let’s say a Match instance doesn’t represent the gradually changing state of a football match, as before; instead, we’ll have it represent a single, unchanging snapshot of a match’s state. Adding a goal to a match will no longer modify an existing Match object; it will instead create a new Match object that captures the new state.

Implement such a functional-style Match class. The details are in Scaladoc form within module Football4.

(For this assignment, we’ve chosen a class that is quite easy to reimplement as functional. Translating between paradigms is not always as straightforward.)

Instructions and hints

  • Make sure that addGoal no longer returns Unit.

  • As stated in the docs, the Match class demands a constructor parameter that indicates the scorers up until the present point of time. For that purpose, the class uses an immutable collection; a Vector works fine. When you create a new match state object, you’ll need to construct a new vector that contains all the earlier scorers plus the new one at the end. For that, you can use the :+ operator, which works as shown below.

    val numbers = Vector(5, 1, 2)numbers: Vector[Int] = Vector(5, 1, 2)
    val longerVector = numbers :+ 10longerVector: Vector[Int] = Vector(5, 1, 2, 10)
    

A+ presents the exercise submission form here.

More Differences Between the Imperative and Functional Paradigms

The table below lists some more features that are typical of functional and imperative programming. They all connect in one way or another to the key difference identified above: mutability.

Imperative programming

Functional programming

Programs and subprograms (such as methods) are seen as sequences of commands. Commands can directly affect program state.

Subprograms are seen as being akin to mathematical functions. They simply return a value as appropriate for the given parameters. (This is part of the reason why the paradigm is known as “functional”.)

Typical collections include arrays (Chapter 12.1) and buffers, which are mutable.

Values are stored in immutable collections. These include vectors (Chapter 4.2), lists, and lazy-lists (7.2); terminology varies greatly between languages, though.

When reasoning about a program, one must be alert for state changes that happen behind the scenes and one must pay particular attention to the sequencing of commands. For instance, these commands:

val a = myObj.doStuff()
val b = myObj.doOtherStuff()

may produce different values than these ones:

val b = myObj.doOtherStuff()
val a = myObj.doStuff()

This is because the methods can have effects on state.

It is often simpler to reason about programs. In many contexts, execution order becomes a secondary concern. For instance, these commands:

val a = myObj.thing
val b = myObj.anotherThing

will always produce the same values as these ones:

val b = myObj.anotherThing
val a = myObj.thing

This is because there are no mutations of state.

As another example, the behavior of myFunc(myObj.doStuff(), myObj.doOtherStuff()) may crucially depend on the left-hand parameter being evaluated first.

In myFunc(myObj.thing, myObj.anotherThing), it is generally irrelevant which of the two parameter expressions is evaluated first.

The concept of reference (or memory pointer) is central to reasoning about programs. References influence how expressions change in value at different points of a program run. If multiple variables refer to the same object, an effect on that object through one variable will affect the use of all the other variables as well (see, e.g., Chapter 1.5).

References are not as important, which simplifies things. Since evaluating expressions doesn’t have effects on other expressions, it cannot happen that a state-change via one variable affects the use of another variable.

On efficiency

Historically, imperative implementations of programs have often been more efficient than functional implementations. The imperative approach makes certain resource optimizations possible.

But efficient implementations of functional programming do exist, too; real-world efficiency depends on the program and the toolkit being used. An important further consideration is that functional programs can be flexibly parallelized: since function calls don’t affect state and since the evaluation order of expressions is relatively free in functional programs, multiple processor cores can evaluate multiple expressions simultaneously. Functional programs can thus be parallelized for substantial performance gains; this is something that Programming 2 will discuss.

On readability and benefits in complex projects

Imperative code is — proponents claim — easier to read than functional code is. The code of a program directly expresses the steps of a program run.

Functional program code is — proponents claim — easier to read than imperative code is. Functional programs are arguably easier to rid of bugs and develop further. Debuggers and similar tools are perhaps not needed as often.

Many of the benefits of functional programming arise from how it is customary to write a lot of small, effect-free functions and use them in combination. Such functions are easier to reason about and easier to test in isolation, as the programmer doesn’t need to consider effects on state and their potentially complex interactions. From such carefully designed pieces, expert programmers can build demanding, fault-free systems for real-world use.

A bird’s-eye view

One last table. This one takes a look at the cultures and ways of thinking that have evolved around the two paradigms.

Imperative programming

Functional programming

Computer programming is perceived as commanding the computer: “I tell the machine what to do.” Commands take the form of consecutive statements in a programming language. These statements describe, in order, what the computer should do.

Computer programming may be perceived as the description of solutions: “I’ll give the computer a precise definition of what I’m trying to achieve.” Comparatively speaking, a program run is a roundabout consequence of the computer working to achieve the described goals. (This is considered characteristic of the declarative paradigm in general.)

The programmer may work on a low level of abstraction: the program’s structure may bear a relatively close resemblance to machine code and what goes on in the computer as it runs the program (although the degree of that resemblance depends greatly on the programming language, among other things).

The level of abstraction is high: programs are far removed from machine code.

Programming is often viewed as a branch of engineering.

Some proponents view programming as a branch of mathematics.

Is arguably a more natural way for humans to solve problems and easier to learn.

Is arguably a more natural way for humans to solve problems and easier to learn.

Object-oriented programming is traditionally often combined with it.

Object-oriented programming is only sometimes — but increasingly — used in combination with it.

Historically dominant in the software industry and among hobbyist programmers. Also popular in the academic world.

Historically less popular in the industry, but there has been a surge in popularity since the 2010s. Popular in the academic world.

Reading Assignment: Four Programs and Four Paradigms

Below, you’ll find four different implementations for the same functionality: simple “storages” that each contain a certain amount of a specified substance. Compare the implementations. Can you spot some typical characteristics of the different programming paradigms that we’ve discussed?

Implementation A

class Storage(val substance: String, val capacity: Int, initialAmount: Int):

  private var storedAmount = initialAmount

  def freeSpace = this.capacity - this.storedAmount

  def add(amount: Int) =
    this.storedAmount = min(this.storedAmount + amount, this.capacity)

  // ... other methods ...

end Storage

Usage example:

val barrel = Storage("milk", 1000, 50)
barrel.add(500)
val stillFree = barrel.freeSpace

Implementation B

class Storage(val substance: String, val capacity: Int, val storedAmount: Int):

  def freeSpace = this.capacity - this.storedAmount

  def add(amount: Int) =
    Storage(this.substance, this.capacity, min(this.storedAmount + amount, this.capacity))

  // ... other methods ...

end Storage

Usage example:

val barrelState = Storage("milk", 1000, 50)
val newState = barrelState.add(500)
val stillFree = newState.freeSpace

Implementation C

class Storage(val substance: String, val capacity: Int, var storedAmount: Int)
// No methods. A storage is a record formed by the three variables defined just above.
def add(target: Storage, amount: Int) =
  target.storedAmount = min(target.storedAmount + amount, target.capacity)

def freeSpace(Storage: Storage) = Storage.capacity - Storage.storedAmount

// ... other functions ...

Usage example:

val barrel = Storage("milk", 1000, 50)
add(barrel, 500)
val stillFree = freeSpace(barrel)

Implementation D

class Storage(val substance: String, val capacity: Int, val storedAmount: Int)
def add(target: Storage, amount: Int) =
  Storage(target.substance, target.capacity, min(target.storedAmount + amount, target.capacity))

def freeSpace(Storage: Storage) = Storage.capacity - Storage.storedAmount

// ... other functions ...

Usage example:

val barrelState = Storage("milk", 1000, 50)
val newState = add(barrelState, 500)
val stillFree = freeSpace(newState)

Questions

Which two of the four implementations best represent object-oriented programming?

Two of the four implementations clearly have features of imperative programming. Which two?

Two of the four implementations are compatible with the tenets of the functional paradigm. Which two?

Which of the four implementations is the closest match with procedural programming?

On Programming Languages and Programming Paradigms

Frequently asked: Which paradigm does Java / JavaScript / C / C++ / Python / Pascal fall under?

  • Java is an imperative object-oriented programming language. The latest versions of Java provide some support for a more functional style of programming.

  • Pascal is/was a programming language designed for education whose heyday was in the 1980s and 1990s. It primarily represents the procedural paradigm.

  • The C language is meant for procedural programming. Its derivative C++ is mostly used for either prodedural programming or imperative OOP.

  • Python is a multi-paradigm language. Often, people use it for imperative programming, but you can program functionally in Python, too. The language has support for objects; some use it for OOP while others program in a more procedural style.

  • JavaScript is also a multi-paradigm language.

In principle, nearly any language can be used for programming within nearly any paradigm. Even though the language itself might not have a particular feature — such as objects — the programmer may strive for a programming style that emulates that feature. Moreover, there are libraries that support unconventional styles of programming in a particular language. Nevertheless, adopting a programming style that runs counter to the language’s design is often inconvenient and consequently rare. Some languages are a better fit for some paradigms than others.

Isn’t Scala known as a functional programming language?

Yes, that too. Scala is a multi-paradigm language that is designed to support several styles of programming. What is unusual about Scala is the seamless amalgamation of object-oriented and functional programming: you could say the language and its libraries tempt the programmer towards an “object-functional” style and especially support this combination of paradigms.

Scala’s advocacy of certain programming practices is reflected in various details. One small example is the way the immutable (functional) collection type Vector is available by default but the mutable (imperative) Buffer needs an import.

There are quite a few programmers who use Scala for pure or near-pure functional programming.

As said, many of our programs in O1 haven’t been in the functional style. On the other hand, we have created various immutable classes and made ample use of effect-free higher-order methods, which are integral to functional programming.

Antiquating current paradigms

The beginning of the chapter suggested that even though the well-known paradigms are worth learning about, they might be on the verge of becoming obsolete. That is in part because of the rise of “multi-paradigm” languages, as discussed above. If this theme tickles your fancy, you could take a look at Joshua Gross’s brief commentary on Quora, and of course there’s plenty more to be found on the web.

On What’s Natural

What is a “natural” way to program?

Chapter 2.1 suggested that the construction of conceptual models from abstractions (such as objects) is “human-centered” — in a limited sense of the word, at least.

And this chapter has brought up that people have expressed contradictory opinions about which paradigms are more “natural” than others.

In the paradigm comparison, it was funny how different people made the same sort of subjective claims about their favorite paradigms: “Imperative/functional code is — proponents claim — easier to read than functional/imperative code is.”

Instead of evaluating those opinions here, how about we instead take a step backward and consider all the paradigms that we’ve discussed as a whole?

Is it even necessary for programming to resemble any of those paradigms? Does programming need to look like it looks today? How could we best utilize the entire cognitive capacity of the human species as we describe dynamic processes? Can we envision a form of programming that is so natural that people could create small programs in seconds even as they debate or reflect on something? What abilities could such a new medium bring forth in humans?

If you’re interested in programming, the future, user interfaces, media, and/or thinking, the following video may be for you. I also heartily recommend Bret Victor’s other output.

Summary of Key Points

  • In many programmers’ minds and words, the field of programming is divided in paradigms. A programming paradigm is a way of practicing and thinking about programming.

  • Programming paradigms overlap with each other. They have no unambiguous or universally accepted definitions. A programmer may draw on multiple paradigms even within a single program.

  • Many people perceive there to be a major paradigmatic fault line between imperative and declarative programming:

    • In imperative programming, the programmer issues sequences of effectful instructions to command the computer. Procedural programming and the most common flavors of OOP are forms of imperative programming.

    • In declarative programming, the programmer focuses on describing the relationships between parts of a solution rather than the ordering of sequential commands.

  • In O1, we practice object-oriented programming. We often combine it with the imperative style but draw on functional programming as well.

    • Functional programming is a well-known form of declarative programming.

    • It’s possible to write object-oriented programs, too, in a functional style. Scala, as a language, encourages this.

  • The most fundamental difference between functional and imperative programming is that in (pure) functional programming, all data is immutable and all functions are effect-free and independent of mutable state.

    • Another difference of note is that in functional programming, it is especially common to pass functions as parameters to other functions.

    • The higher-order methods from previous chapters (map, filter, etc.) are typical of the functional paradigm.

  • Different paradigms have their own strengths and weaknesses. In the future, the divides between the paradigms may grow less steep. Keep an open mind as you build your expertise as a programmer. Don’t fall victim to dogma!

  • Links to the glossary: programming paradigm, object-oriented programming, imperative programming, functional programming, procedural programming; mutable, immutable, effectful function, effect-free function.

Feedback

Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.

Credits

Thousands of students have given feedback and so contributed to this ebook’s design. Thank you!

The ebook’s chapters, programming assignments, and weekly bulletins have been written in Finnish and translated into English by Juha Sorva.

The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.

The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó, and Aleksi Vartiainen.

The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.

The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio did the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.

The other diagrams and interactive presentations in the ebook are by Juha Sorva.

The O1Library software has been developed by Aleksi Lukkarinen, Juha Sorva, and Jaakko Nakaza. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy of using O1Library for simple graphical programming (such as Pic) is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and Krishnamurthi and Picturing Programs by Stephen Bloch.

The course platform A+ was originally created at Aalto’s LeTech research group as a student project. The open-source project is now shepherded by the Computer Science department’s edu-tech team and hosted by the department’s IT services; dozens of Aalto students and others have also contributed.

The A+ Courses plugin, which supports A+ and O1 in IntelliJ IDEA, is another open-source project. It has been designed and implemented by various students in collaboration with O1’s teachers.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits appear at the ends of some chapters.

a drop of ink
Posting submission...