Thursday, November 4, 2010

Programming and Reprogramming the Mind

As a programmer, I was brought up on a diet of BASIC, Pascal, C and a little LOGO. Okay, BASIC was a simple, if not very good language, but it was a start. Pascal was a big improvement and I learned more in one Summer of programming Pascal than in the previous 5 years of BASIC, but it really is a very similar way of programming. C, while different, was much the same in reality. They are all imperative programming languages and as such you use the same mindset to program in any of them.

For those who already know what an imperative programming language is, you can skip this paragraph. For the rest of you who want to know what this means, I shall try to explain what imperative programming languages are. Firstly, one has to realise that a computer is what is sometimes referred to as a finite-state machine. This means that at any time, a computer has to be in one of a number (potentially huge number) of finite states. An imperative programming language will define a set of instructions that the computer is to perform and in which order, basically going from one state to another. In natural language, we are used to these kinds of instructions. Most instruction manuals use this kind of approach. A simple example for doing the weekly shopping is this:
1. Get out pen
2. Get out paper
3. Write shopping list
4. Go to shop
5. Gather items on shopping list and place in basket
6. Bring items to cashier
7. Pay cashier for items
8. Bring items home
It's all fairly easy to understand and it's a way that many coders use to think about programming. But, it's not the only way.

Now, some might think I'm going to move on to discussing Object Oriented Programming, and they're right. The next step for me was to learn OOP as I learned C++, Java and later on, C#. Strangely enough, I really didn't see much of a change. Yes, these languages allowed me to do some things I longed to do, and do so in much easier ways than I had with C or Pascal, but on the whole, there wasn't a real shift in thinking. For one, I, like most procedural language programmers, had already discovered and implemented some of the more useful aspects of OOP and done so through my own coding style. For instance, for a long time before learning OOP, I had generally written functions to handle any data-structures I defined. This made it much easier to handle code complexity, modify the way the structure behaved and find and fix bugs. It's generally similar to the idea of having classes that keep all data private and only using methods for data manipulation. Of course, all competent programmers recognise the benefits of using this approach and most apply it, even those who prefer to not use OOP.

Of course, this is hardly the only benefit of OOP. In fact, by virtue of the fact that I was applying this approach long before I was introduced to OOP, one might even say that it is not a benefit of OOP, but of sane coding practice in any imperative programming language. The reason that OOP didn't really require any mindset shift is quite simple. It's still imperative programming. I was still writing in much the same way as I had before, just using different syntax and some very handy short-cuts (such as inheritance and polymorphism).

For those who have stayed with me, but required the paragraph describing what an imperative language is, with OOP, I was still using the same type of list, but now I could get away with a few less steps in the list and a slightly differently expressed list. Especially so when I was using a much longer one. One might say that rather than having to write code to handle the concept of a bag and a shopping basket, I could just write a generic piece of code for a device which held items and just specialised it a little for each. Effectively, it reduced the level of redundant code.

The type of programming which has forced me to rethink things is that of Functional Programming (FP). For a long time, I have dabbled on and off in FP. I never really wrote too much, always just little programs to satisfy my curiosity. I found the beauty of LISP to be inescapable. It provided a different way of thinking about things. But, I never really embraced it as I should. I guess I lacked the motivation and necessity. Now, I am not saying that I have suddenly found either, but I have found a couple of interesting essays on the topic and I am going to have to give LISP a lot more thought.

But, I get ahead of myself. Again, I should explain to the more determined of you what FP is. A lot of the difference dates back to the 1930s. When mathematicians were theorising how computational machines would work, they developed a few different approaches and one is that of FP. It's not like imperative programming. One does not treat the computer as a state machine, but rather as a single function which takes other functions as arguments, and so on and so forth. I'm going to steer clear of the mathematics involved as they're not exactly trivial, but this approach has some very interesting results.

But, I hear you cry (and before I go further), I don't just want to be able to deal with numbers, I want to deal with words and things like that. Of course you do, and so do we all, but all such stuff is treated as numbers by a computer. And all ways that the computer can manipulate these numbers can be expressed in mathematical terms (because that's what they are).

FP does have some interesting effects. One can manipulate the functions in ways which are just not easy or possible with programs written in imperative programming languages. You see, in an imperative programming language, there is a defined order to the instructions. They depend on being in order, otherwise you get the wrong results. Lots of effort is put into finding the parts of programs which don't interfere with each other, so they can be run side-by-side to use multiple CPUs. I have a friend who makes a very comfortable living taking other peoples' programs and doing just that. It's a task which is very difficult to automate, and even the best automated efforts today often give only a fraction of the improvement that my friend can very easily get.

In FP, we can actually do a whole lot of rearranging of stuff because we have made sure that the intermediate states don't matter at all. We can break up the function into it's parts and run them in any order we want. A simple example in mathematics is where we add all the numbers from 1 to 4:
1+2+3+4=
3+3+4=
6+4=
10
Of course, we could also have done the following:
1+2+3+4=
3+3+4=
3+7=
10
This is a trivial example. But, now instead of doing things one step at a time, imagine we could do two:
1+2+3+4=
3+7=
10
Okay, in this simple example, it only saved us a tiny amount of time, but with much more complicated systems, we can get much bigger savings and do so without any extra effort from the programmer.

There are many other benefits of FP, such as being able to delay evaluating functions until we are certain that we will have to do so, or being able to debug functions much more easily.


Unfortunately, the natural code that a modern computer uses is, in all cases I am aware of, an imperative programming language. This means that FP code will effectively have to be translated into imperative programming code, which, thankfully, has been possible. In fact, it has been proven that anything that can be done through a FP language can also be done using an imperative programming language (and vice-versa).

Now, the smarter of you will see one very obvious result of this and that is that if one can write FP code through imperative programming code, why would you bother with the former at all? And how can the benefits that I talk about actually exist at all? This is an interesting logic conundrum. I'll see if I can explain that too. You see, when we write code, we are not writing the code that the computer will run, we are writing something which will be interpreted and then run by the computer. This can be interpreted by a compiler (a program which turns programming language into a native code program) or by an interpreter (a program which turns the code into native code as it is being executed). We will assume that we are using an interpreter, but the result is the same. At this stage, a lot can be done with the code. The interpreter is able to take short-cuts, if they are available to be taken. Unfortunately, the interpreter can only do so much and even good ones will not be able to reorganise imperative programming instructions beyond a very limited amount. You see, it is very difficult to be certain that the state of the machine as influenced by one instruction will not be important to another. The FP interpreter can do much more and with greater ease. It can decide to delay interpreting a function until it has to, or decide to interpret two functions side-by-side, and so on.

But, surely this means that FP is exceptionally suitable for very fast programs, especially where one has multiple CPUs. Unfortunately, not for the moment. As said before, modern computers are not built with FP in mind, not only that, but the fact is that there is lots of work for the interpreter to do and often the benefits come with a high cost in terms of memory and CPU time.


So, why is functional programming so interesting to me and why now?I guess, I've always found what little LISP I have coded to be quite rewarding and at the same time, the required shift in mindset has never been one I've fully embraced. One big reason is that some of the benefits of functional programming are quite amazing and possibly more important now than ever before. But, the biggest reasons are that I suspect that in the future we will move to FP for performance reasons. Not just the performance of executing the code, but the performance in developing the code. As a programmer, I spend so much of my time trying to keep complexity under control. A well written piece of code will be relatively easy to understand and avoid unnecessary complexity. This complexity is the cause of most of the bugs I find in both my own code and that of others. It causes people to make coding mistakes both in terms of logic errors and typos and it makes debugging much more difficult. For relatively simple code, one will spend almost the same amount of time coding as debugging. For complicated code, one will spend far longer debugging. Some code has taken me about 100 times as long to debug as write, and that's with a huge amount of effort to make the code as easy to debug as possible and keep complexity to a minimum.

The single greatest benefit of FP is that the complexity is kept in control in a much more efficient way. While OOP keeps some of the complexity at bay, it fails to address the single biggest source for all of us, the source that makes debugging a hard task and that is the curse of the side-effect. You know, the things that functions do that go beyond the scope of just that function, such as changing the contents of a variable passed in by reference. Well, those things and the fact that imperative programming languages need them and rely on them, also means that we can trample memory, corrupt data structures and so on. These are things that many languages try to avoid, but which all imperative programming languages suffer from and which they actually require. FP needs to do nothing to keep such side-effects under control because they simply can't happen. An FP function is self-contained. Nothing it does will interfere with any other function, except to return its return value. This is why any FP functions can be run side-by-side and why they are both easy to debug and much less likely to result in hard bugs. It's a beautiful model and one I must learn to embrace.

Anyway, I've waffled on for long enough. If you are a coder and you want to see a different way, please look into learning a functional programming language, such as LISP or Scheme (actually, the latter is probably a better starting point).

I leave you with a comic from XKCD:

No comments:

Post a Comment