Should You Start Programming with a Procedural Language?

Why trading procedural programming for functional is like learning to ride a unicycle

A few weeks ago, I invited readers to consider which is the best programming language to learn first. I made my own recommendation, and people had a good discussion about it in the comments. If you’ve been looking around at different languages, you’ve probably heard about different programming styles, or paradigms, that languages fall into. The terminology can get confusing, and isn’t at all obvious if you’re new to programming. To confuse the matter even more, the various languages don’t often fall strictly into one paradigm, and the paradigms themselves sometimes shift and overlap. To keep things simple, though, I’ll introduce the three most commonly discussed paradigms: procedural, functional, and object-oriented.

If you just want to be told what to do, read this first.

Order and Control:  Procedural Programming

Procedural programming, also called imperative programming, features an orderly flow of control through the program, starting at the top and continuing through the end. When you need to carry out a discrete task, especially one that might be carried out multiple times, you pass the control to a function, which does a specific set of actions, and then returns a value. Examples of procedural languages include BASIC, Pascal, and C, although there are many others.

Here’s a very brief example of some C code:

int add(int a, int b);
int main(){
    int firstNum = 6;
    int secondNum = 15;
    int sum;
    sum = add(firstNum,secondNum);
    printf("sum= ",sum);
    return 0;
}
int add(int a,int b){
    int result;
    result = a + b;
    return result;
}

You’ve got two variables here, firstNum and secondNum. Each gets declared and assigned a numeric value. Another variable called sum is also declared. Then sum is assigned the result of the add() function. The control flow jumps to the add() function down below, where the two numbers are added, and then the result is returned, and the program picks up where it left off, printing out the result and then terminating. There’s more going on here than that, but you get the general idea.

An advantage of procedural programming for beginners is that once you learn a little of the syntax, you can follow the code and see what it’s doing just by reading off the screen.
 

The Data Is the Program:  Functional Programming

Functional programming, as the name implies, emphasizes functions, in a more central manner than procedural programming. In functional programming, each function is linked such that the output of one function feeds the input of the next. The behavior of the program depends on the data that’s input. It’s often said that procedural programming describes how you want something done, whereas functional programming describes what you want done. In functional programming, data (often defined as the state of the program) can’t be changed. There are no variables; there are only inputs and outputs. Any changes to the state of the program are considered side-effects. Depending on who you ask, side-effects are either a harmful contaminant to your program, or a useful way of getting things done. Examples of functional programming include Common Lisp, Haskell, Erlang, and F#.

One of the many things that functional languages do very well is operate on lists, because functional languages excel at recursion. Here’s an example in Erlang of how to sort the items in a list. (There are lots of algorithms for sorting lists with different techniques; this one in particular is called “QuickSort,” but that’s not important here.)

sort([Pivot|T]) ->
    sort([ X || X <- T, X < Pivot]) ++
    [Pivot] ++
    sort([ X || X = Pivot]);
sort([]) -> [].

X represents the list in this function. It doesn’t have to be a list of numbers; it can be words, prices, aardvark — as long as the programmer can define some way in which an item in the list is “more” or “less” than another item, this sort will work. QuickSort relies on selecting one item in the list as the Pivot, which in this case is the first item in the list. [Pivot|T] means “the first element is the Pivot, followed by the rest of the list, which is T.” The rest of the function defines the output as “a sorted sub-list of every element less than Pivot, followed by Pivot, followed by a sorted sub-list of elements greater than or equal to Pivot.” So far, so good, but how do you get a sub-list to be sorted? You use the sort function on it — simple. That sub-list is then divided into sub-sub-lists, which in turn have the sort function called on them, repeating until there’s a sub-list with only one element, which is automatically sorted. Then the spiral of function calls unwinds until you have a fully sorted list. That’s recursion, which is what functional programming is so good at. The other key element of functional programming is defining a function in terms of other functions — recursion is simply a special case where the function is defined in terms of itself.

You’ll notice that the functional code is very short, yet does something much more complex than the procedural example. It’s also rather difficult for humans to read and understand. In fact, if you got a headache from my description of recursion, you wouldn’t be unusual. Functional programming takes a lot of practice to “get,” and requires a different mindset than procedural programming. Where procedural programming goes in straight lines, functional programming goes in spirals.
 

Parts Assembly:  Object-Oriented

An object-oriented language seeks to model something in the real world through the use of objects. Objects usually consist of two parts: data (often called members) and functions (often called methods). The classic way of distinguishing members from methods is that members are defined as something an object has, whereas methods are defined as something an object does. For example, a cat object could have a color and a size as data members, but could have purr() and eat() as methods.

The primary distinction of object-oriented programming is that the data and the methods are intrinsically tied together in the same object, which can then be passed throughout the program as needed. Some objects accept other objects as input, which in turn act upon still other objects. Following the control flow through an object-oriented program usually isn’t intuitive. Examples of object-oriented languages include C++, Java, and C#.

Here’s a very simple example in C++:

class Cat
{
  public:
    Cat(int initialAge);
    int GetAge() const;
    void SetAge(int age);
    void Meow();
  private:
    int catAge;
    char * catName;
};
Cat::Cat(int initialAge)
{
    catAge = initialAge;
    catName = new char[10];
}
int Cat::GetAge()
{
    return catAge;
}

void Cat::SetAge(int newAge)
{
    catAge = newAge;
}

void Cat::Meow()
{
    cout << “Meow!n”;
}

int main()
{
    Cat Fluffy(5);
    Fluffy.Meow();
    cout << “Fluffy is “ << Fluffy.GetAge() << “ years old.n”;
    return 0;
}

Even this simple example is much more involved than the previous two. In part, that’s the nature of the example — an object-oriented language needs something to model, or else it looks a lot like a procedural language. The bulk of the code is taken up with defining the model of the Cat() class. Often, you’ll be able to use a library, a file containing classes defined by somebody else, and you won’t need to create your own class. In fact, you often won’t know exactly how the code inside the library works; you’ll just know the functions and data that you have access to.

All the action in this example goes on in the main() method, where a cat object named Fluffy is created. The cat’s Meow() function is used, and then the GetAge() function is used to access a data member. If you follow the control flow of this code, you’re constantly jumping back and forth between the instructions in main() and the functions in the class definition, which then access data that’s defined somewhere else.

You’ve probably noticed that the examples I’ve given here all do different things, which makes it harder to compare them. That’s because the languages were intended for different purposes, and trying to use them for a purpose other than what they were intended for leads to awkward code. You can write a simple addition function in a functional language, but it doesn’t look particularly functional. You can also implement the QuickSort algorithm in a procedural or object-oriented language (there are tons of examples out there), but it requires substantially more code, and isn’t as neat as the functional example. It’s like trying to compare a claw hammer with a hand trowel; you can dig a hole with either, but it’ll be a lot easier with the trowel, because that’s what it’s designed for.

Just Tell me Where to Begin!


So if you’re just starting to learn a language, which type is best? There’s no single good answer to that question. Each has its own strengths and weaknesses. There are plenty of people who’ll argue vociferously for their favorite language, or assert that the style of programming they’re used to is best. Mostly, that just leads to lots of arguing on the Internet. Most telling of all is that most languages aren’t “pure” to one style or another. Object-oriented languages often evolved from procedural ones, like the evolution from C to C++. Many object-oriented languages are now including functional ideas, often called lambda functions, that allow functions to be used as inputs to other functions. And functional languages usually have some “syntactical sugar” that makes code easier to read for those who are accustomed to procedural languages.

You Know Your Job

If you have a job, or are looking to get one, in a field where one style of programming is commonplace, say academic mathematics for functional programming, then you should learn the style that’s dominant in your field.

Starting from Square One

But if you have choices, and you’re just starting out, never having learned a language before, then I suggest starting with a procedural language, or at least an object-oriented language with procedural roots, such as C++ or JavaScript. The first language you learn is always going to leave a mark on you, and if you want to learn a new language, you have two choices: either hope that the new language is similar enough to the old that you can map what you already know onto the new language, or else un-learn what you’ve learned and replace it with new ideas.

Un-learning and replacing with new knowledge is good exercise for your mind, and something we should probably all do more of, but it takes time and effort. Procedural programming by itself may not get you very far, but it’s a good starting point to learn a lot of other languages. Learning procedural programming teaches you about control flow, along with loops and variables, which are often the guts of functions. It teaches you about data structures, which are the building blocks of objects. Once you’ve learned those basics, you can scale up what you know to the other paradigms.

Once You Have A Feel for It, the Ride Propels Itself

Learning procedural programming first is a lot like learning to ride a bike by using training wheels. If you spend too long with the training wheels on, you won’t be able to learn to ride without them. Going from procedural programing to object-oriented programming is like taking the training wheels off. It’s strange at first, and you’re bound to fall a few times, but you can use what you already know and adapt. Learning functional programming from procedural is like switching to a unicycle. It looks like the same thing from the outside, but it requires an entirely different way of thinking about what you’re doing.

There’s a school of thought that training wheels are a poor way to learn to ride a bicycle, and that you should simply start by learning what you want to learn to do, not working up to it. I acknowledge that argument, and maybe the people who espouse it are smarter than I am, or better at learning new skills. For me, I like to learn a new skill in small steps, so I’m glad I learned procedural programming first.

tags: