My own history
I have been coding since I was thirteen, when my Father came home with old IBM clone that he traded for during the 80’s barter craze. Being nearly forty years old now, I have stubbed my toe and left it bleeding in more than a dozen languages now. If I were a construction worker instead of a knowledge worker, I would probably be missing fingers, toes, and possibly a hand or leg.
I worked through the 90s as the Internet was in it’s very early years. I had a Geocities page for my Warcraft tips and strategies. It probably didn’t blink, but it must have been fugly. I worked for companies that turned email into snail mail, companies who turned snail mail into email, and companies who made adware for Windows machines and a little bit of everything in between.
When I coded in C, we hated those ‘weird’ C++ guys. We would tease them about using a language before the improvement, since it wasn’t ++C. Then I joined that group to write DCOM and COM+ for Intel. We made fun of the VB programmers. Then I became a VB programmer when ASP exploded, since it was essentially VBScript, and I don’t like context switches. Then it was semi-colons that were stupid.
By the time I got to Ruby I hated working with the Microsoft stack. The guy that introduced me to Ruby in 2006 was working in C# with me, and constantly opined at the lack of jobs in Ruby and LISP. We hacked a bit during lunches, but I never had time to get serious about Ruby, as I worked at a startup. Like 80 hours a week worked at a startup.
He never made fun of anything in languages. He didn’t point out differences in the languages in a snide way. Every tool had uses, and every language had interesting points and beautiful aspects. It was almost his attitude alone that made me want to work with Ruby. The language seemed very much like this programmer. Friendly, smart, concise, and there wasn’t a lot of, no you cannot do this, you shouldn’t do that.
I was introduced to the concepts in LISP and some functional programming by him as well. He also was a versed smalltalker. He really opened my eyes. Then I got a Ruby job and moved to Amsterdam for it. I can’t even remember this guy’s name beyond Courtney, but I will forever be grateful to what he shared with me.
I have spent more hours in my life with debug statements, than probably anything else, except possibly sleeping or growing older. I used to think that this was just the state of things. You wrote some code, then you ran it, and it either, A) ran and acted as expected or B) politely suggested I write some debug statements to what was really going on.
Debugging is basically detective work. When the code you interact with is convoluted, and obfuscated through constant laziness and ignorance, you constantly have new mysteries to solve. The mystery you really are solving here is not syntax, it is not structure, it is almost always state, and the way that state is formed and managed.
Why state is hard to reason and debug
When I speak of state what I am really speaking of, are memory addresses. In the early days of computers, memory was very pricey and in limited supply due to space. This meant programmers were forced to write efficient code, and efficiency meant small size and fast speed. So developers wouldn’t think twice about reusing a memory address instead of allocating more, it was in fact preferred given the space you needed was equal or less to what was already allocated.
Still today, programmers will reassign a variable over and over, changing it’s state. Memory is now very cheap and machines have lots of capacity, and yet variables are rewritten habitually. And not just setting something else to that memory address, but also changing what was there originally.
So imagine we have a Person class. The class has several pieces of internal state, we will just focus on ‘name’ and ‘favorite_color’, for our example, I will use my Uncle Bob, because he has taught me so much.
So I create a person class instance, Bob. Then I save him off to a database, the how doesn’t really concern us, so I will just use a descriptive stand-in double.
Then later on, I find that I need to fill in the last name as well and then re-save it:
But then later on again, I alter it again, but adding some other field and overwrite it again. And so on and so forth.
Now trying to rationalize about the state of that object is difficult because it changes and changes and there is no audit trail of what it used to be like. It is like a video stream that cannot be rewound.
I will now do the same thing as above, but this time, I will create new copies of Bob before I alter his state:
I now need to change his name to his full name, so I create a clone, alter that clone, then save.
Now I change it again, this time because we find out my Uncle’s favorite color is, in fact, green.
But you might say, what about in the database we are ignoring? What about primary keys? It isn’t the concern here, but you could use last updated at and update a join table with a static guid, and the current id, but again, not the point here.
Now I want you to think of this process not as a video stream that cannot be rewound, but a set of many still snapshots. If you were combine the stills, you could recreate the stream again. However, if you want to, you can lay these versions of my Uncle together out in front of you on a desktop and see all states of Uncle Bob before you at once, and not be stuck debugging the current state of him.
In a way, this style of state management gives you an almost superhuman power of all seeing, where you can look at a person and instead of seeing them as they are you can see all versions of them as any given state of time. Pretty cool, next you might be able to dodge bullets!
The death of a generation of programmers
For the entirety of computing up until 2012-2013, memory and speed were doubling every year as noted by “Moore’s law”. During that time, the amount of abstraction, or layer on layer of code to make dealing with the complexities of voltages and gates and registers easier on up to windowing tools and the GUI.
These two have gone at about the same pace, the latter being limited by the former. However, seeing “ahead” of time by measuring the slow of growth per cycle, the CPU industry changed gears and instead of making the single core much faster, they bundled cores together to create a multi-core architecture.
Programmers who do not adapt to this new age of multi-core will cease to be hireable and their families will starve like in a Dickens’ novel. Do not let this happen to you!
The multi-core, memory address problem
Now that processors are no longer growing exponentially, and multi-core architecture has taken over, we have threads that operate against different cores. One core can accessed at the same time as other cores, which is where they get their speed improvement over the single core.
Yet there is only one memory register. Well, this is not entirely true, but there is likely only one memory register your application has access to. That means you could have something like the following:
So I create Bob again, but this time, pretend this is going to happen on core 1:
Bob’s favorite color is about to change again, but it will be done by core 2:
Then core 1 tries to access their copy again, but wait…. it is not in an unexpected state. It was modified by another core, and this can create race conditions that include memory corruption.
That is a highly over-simplified explanation because I am not a hardware engineer nor are the absolute internals of the CPU our domain. What you should gather here is that a developer can no longer ignore threads and multiple cores or how they might interact with each other. We need to write software that naturally works in this brave new world.
To do so, we dig into the braver old world, where smart people have already figured out the problem and the solution before it even existed. In this book I will help guide you on a path from the imperative to the explicit, and the object oriented to the functional. These concepts are not polar, they are orthogonal, that is, you can plug them into each other for a hybrid experience.
Let’s embark into the magical world of value objects, immutability, hybrid functional programming, threads, and homoiconicity, but do so in the comfortable and familiar, friendly world of Ruby!