The latest instance of the course can be found at: O1: 2024
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. Myös suomenkielisessä materiaalissa käytetään ohjelmien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.
Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 10.2: Programming Paradigms
About This Page
Questions Answered: What if we didn’t use objects? How can you program properly without directly mutating program state? What kinds of traditions and factions are there among programmers? What do programmers talk about when they talk about paradigms?
Topics: Some programming paradigms. Object-oriented programming, imperative programming, functional programming.
What Will I Do? Read, for the most part.
Rough Estimate of Workload:? One hour.
Points Available: B5.
Related Modules: There is an optional assignment that involves Football4 (new).
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 web sites 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 some 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, then 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 fact that there are no hard-and-fast definitions of each paradigm that everyone would agree on; this chapter relies on the author’s interpretation. In any case, this 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:
- How does object-oriented programming differ from other forms of programming?
- How does imperative programming differ from functional programming?
Object-Oriented Programming vs. Programming without Objects
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.
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, 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 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
val
s, never var
s.
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 meaningul?
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 fact that the computer consumes 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: an effect is meaningful in case whether or not it happens affects whether or not 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).
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 challenging 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 for loops
(Chapter 5.5) are representative of imperative programming,
as are do and while loops (Chapter 8.3). |
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.1). do and while loops
aren’t used; the 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
}
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
(Chapter 8.1): you can mutate a grid’s state by replacing its contents at a particularGridPos
.
More View
s
There is also a class o1.gui.immutable.View
that provides a slightly different framework for
GUIs whose domain model is an immutable object.
- The GUI class
o1.View
is designed so that aView
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) = new Pos(this.x + dx, this.y + dy)
}
Pos
objects are immutable: all their variables are val
s 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 9.3) 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 = new 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 var
s, 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 returnsUnit
.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; aVector
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 11.1) and buffers, which are mutable. | Values are stored in immutable collections. These include vectors (Chapter 4.2), lists, and lazy-lists (7.1); 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 — since the methods have effects on state — different values than these ones: val b = myObj.doOtherStuff() val a = myObj.doStuff() |
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 — since there are no effects on state — always produce the same values as these ones: val b = myObj.anotherThing val a = myObj.thing |
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. These days, in the age of multi-core processing and cluster computing, the fact that functional programs can be flexibly parallelized is very significant: 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 ...
}
Usage example:
val barrel = new 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) =
new Storage(this.substance, this.capacity, min(this.storedAmount + amount, this.capacity))
// ... other methods ...
}
Usage example:
val barrelState = new 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 = new Storage("milk", 1000, 50)
add(barrel, 500)
val stillFree = freeSpace(barrel)
Implementation D
class Storage(val substance: String, val capacity: Int, val storedAmount: Int) {
// No methods.
}
def add(target: Storage, amount: Int) =
new 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 = new Storage("milk", 1000, 50)
val newState = add(barrelState, 500)
val stillFree = freeSpace(newState)
Questions
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 a programming language designed for education whose golden age 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?
Well, yes, that too. Scala is a multi-paradigm language that is designed to support several styles of programming. However, 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, such as 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.
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 that has 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, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, 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 and Juha Sorva. 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. Markku Riekkinen is the current lead developer; 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 was created by Nikolai Denissov, Olli Kiljunen, and Nikolas Drosdek with input from Juha Sorva, Otto Seppälä, Arto Hellas, and others.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.