Software Model That Matters
Software Model That Matters
A couple years ago I came across a book called Structure and Interpretation of Computer Programs. I started reading it, and quickly realized this book is quite different from other books about programming I’ve read so far. The ideas presented in the book looked familiar, yet somehow those ideas were presented in quite a different light. Unfortunately, I was so busy with other projects, I had to postpone the reading. I didn’t want to simply push through the book, but I promised myself I would read it thoroughly as soon as I was able to organize some time.
Time went on. The projects I’ve been working on were delivered, but sometimes it took great effort to deliver them on time. In retrospect I had to admit something wasn’t working. And it wasn’t only me. My Android and backend colleagues were also dissatisfied with the amount of time they needed to spend on bug fixing, adding a feature, re-working, re-designing, or even re-writing systems they had created. Of course me and my friends could simply blame management, crazy deadlines and other things, but we all agreed something wasn’t right.
I’ve been exploring different approaches to how to alleviate the problems I’ve been facing, and as I was doing my research, I’ve also started wondering what the future can hold for us as developers. Is it possible that software development will look the same in 5, 10 or 20 years from now? Is it possible that someone could have predicted how software development would look in 2017, for example, 20 or 30 years ago? I’ve been doing my research and at some point I found a talk by Bob Martin: Functional Programming; What? Why? When? The Failure of State. The reason I’ve watched this talk with great excitement was not that it was trying to answer the questions I was asking myself, but also the fact that Uncle Bob was referring to Structure and Interpretation of Computer Programs from the very beginning.
Bob Martin’s answer was very simple: the future belongs to functional programming; memory is cheap; days of doubling clock speeds are over, but the Moore’s law means that we may expect increasing number of cores and functional programming is probably the best way to handle concurrency. My initial question seemed to have been answered, but I didn’t feel satisfied. Could it be all that simple? Does functional programming have anything else to offer beside good support for concurrency? What was the reason the assignment statement was postponed for over 200 pages? If SICP was written over 30 years ago, recommending functional programming as the way to go, why hasn’t it taken over the world yet? Or, how great an effort would it be for the OOP developer to get familiar with functional programming? I was very curious what those 200 pages could have contained. And is it really that functional programming is the answer, and it is the future? I’ve decided it’s finally time to read the SICP.
From the very beginning the reader is taken on the journey to the core of software development: abstractions, composition, and a little bit of forgotten recursion. SICP may be seen as great introduction to functional programming, but it also presents different programming paradigms, which are shown as tools for particular problems, rather than arbitrary ways to go. Its essence is not algorithms, nor their mathematical analysis, but rather the techniques to build abstractions and manage complexity in systems. As every book on programming should be, SICP is very hands-on, filled with great examples showing how to build modular and maintainable programs. It helps to identify and apply reason to certain kinds of problems. The authors show us how, with the right approach and appropriate tools at hand, and a little bit of a discipline, complex mathematical, physical, or almost any computational problems can become easy to handle. The book is never dogmatic in any sense. SICP authors also explain what happens when their guidelines are not followed. It is a great deal as it actually teaches programming, not a programming language, its syntax or particular constructs. Examples are written in Scheme, Lisp dialect. The programming language was chosen very carefully. Scheme, unlike many imperative languages, has very little syntax. This approach helps both the authors and the readers to focus on programming topics, rather than on programming language itself.
The answer why the assignment statement is introduced so late in the book is very simple: a lot can be done without it. What’s more, the assignment statement introduces complexity due to the fact that it forces programmers to deal with time and changing state, yet maintain the identity of each object. This requires considering the relative orders of the assignments to make sure that each statement is using the correct version of the variables that have been changed. Together with concurrency, assignment statement is almost unmanageable. All this means that building abstractions and compositions in systems that extensively use the assignment statement can become a very tricky business. But are there any other ways to model state or represent time in programming?
SICP authors also explore the possibilities to avoid issues introduced by the assignment statement by modelling state with streams. The streams approach allows us to preserve all the benefits provided by functional programming, but it turns out there are other difficulties. There is this example of a joint account shared by Peter and Paul. The bank account is modelled as a process operating on streams. Those streams represent Paul’s and Peter’s transactions. On one end there are input streams from Peter and Paul, and on the other end there is a stream of responses. The problem, however, is the great difficulty in merging those individual streams of transaction requests by Paul and Peter. One approach could be to take alternatively one request from Peter and one request from Paul, but if Paul, for example, is accessing the account only occasionally, Peter would have to wait for Paul before he could start a new transaction. So even in a functional setting, explicit synchronization can’t be avoided. SICP authors point out two essential ways of modeling the world: a collection of interacting stateful objects; and a single, timeless, stateless entity. In systems where the unshared state of the objects is much larger than the shared state, the model object may turn out to still be useful, but there are systems (quantum mechanics, for example), where object viewpoint fails and leads to paradoxes and confusions.
Side Effects, Interaction and Time
Despite its great values, functional programming also has to deal with some limitations. It is not possible to create programs which would allow us to model state or time without using an imperative approach. Perhaps Functional Reactive Programming (a.k.a. Denotative, Continuous-Time Programming) or Category Theory have answers to the problems SICP authors were trying to solve with streams, but it is hard to tell whether or when those will find their way to mainstream programming as both are following rigorous mathematical formalisms of which software engineers are not the biggest fans. Another approach, which tries to avoid DCTP formalisms, at least to some extent, is to simply admit that side effects can’t be avoided, because the programs we write need to affect the surrounding world, interact with it, and that some parts of the functional program need to remain stateful. There are different flavors of this approach, but the most interesting seems to be Monadic IO, Actor model and the model proposed by Clojure. Monadic IO is usually used in purely functional languages like Haskell, where state is kept outside of the functional setting. The Actor model is probably the best choice in highly asynchronous, distributed and unreliable systems, but the messages it’s using for synchronization could be an overkill for simple data reads, or when used within the same process. Languages like Clojure, on the other hand, seem to take a more pragmatic approach by admitting that state is an essential part of the application, and they provide a very interesting time model, based on transactions like those in database systems, allowing to handle time and side effects with the care they deserve.
Functional programming is not only about programming without side effects and pure functions. It is also about managing complexity, building powerful abstractions and compositions, leading to modular and maintainable programs. Those aspects are not the only reason functional programming should be considered a choice in modern systems. The software model inherited from the 1970s in which a programmer sits at a computer he or she has total control of, a computer with one single-threaded processor and one memory (with no notion of memory hierarchy), is no longer valid as the actual hardware model has diverged from this model many years ago. Today we have systems with multiple processors, we work with multiple threads, we don’t have total control of systems we work with, we’re doing multitasking, or competing with other processes for resources, yet the way of modeling such systems at the core arguably haven’t changed. Functional programming may be helpful in building new, better hardware-suited software models like the one proposed by Clojure. It is getting its momentum and often times it is not only a choice for academic use. It comes with an entry fee as the learning curve may be very steep at the beginning, especially for those who have been working in a different paradigm for a while, but in the end it can make us become better developers, even if we decide to remain in the imperative world. I’m not saying functional programming is a silver bullet, but it might be something that can lead us closer to one.