00:00:00.030
Next up, we have Tom Stuart. Tom traveled here from London and is the CTO of FutureLearn, which is an online learning platform. Previously, he was doing his PhD at Cambridge, which he assures me is superior to Oxford. However, he didn't actually complete his PhD because he found the tutoring and lecturing side too interesting to finish his thesis. Instead, he wrote a book about it. So, welcome Tom Stuart, as it is his second time here.
00:00:54.880
Hi everyone! I'm so happy to be here in this beautiful city at this fantastic conference again. Thank you so much for having me. Imagine this: you're at your grandparents' house, and in the attic, you find a dusty old computer. You plug it in, and it boots up — it still works! It turns out that it runs Ruby, but when you fire up IRB, you realize there’s something a little strange going on. You can multiply numbers no problem, and subtraction works fine sometimes. However, if you attempt to perform an operation that might generate a negative number, you receive a strange error: zero is the smallest number. It turns out that this version of Ruby doesn't support negative numbers.
00:01:46.720
So here’s your challenge: how will you add support for negative numbers on this old computer? More specifically, how are you going to implement a class that can represent signed numbers? You need to create numbers that can be either positive or negative, with some factory methods for building positive and negative numbers, and a few instance methods for basic arithmetic. If you implement this class, you could require it in IRB and define some helper methods to make it more convenient to call those factory methods. Then, you would be able to add numbers and get the correct answer, or safely subtract numbers to receive a negative answer without any errors, or multiply positive and negative numbers, or compare them.
00:02:37.870
Let's start by looking at the basic form of a signed number. It has a sign and what I'll call a size, which is its absolute magnitude. This size is always a positive number, so we can safely work with it on this old computer. We can begin by implementing those two factory methods — one that takes a number and creates an instance with the size that corresponds to the number passed in, setting the sign depending on which factory method is invoked. We can represent signs in various ways; I've chosen symbols, but you could use booleans or create your own sign class. In the initializer, we will store the sign and size in instance variables and provide getter methods so that other instances of this class can access these values.
00:03:38.050
Next, let’s implement the equals method. If I'm given another signed number, how do I determine if it's equal to mine? We just check that the sign and the size of the other number match mine. According to IRB, this works fine: equal positive numbers are equal, and equal negative numbers are equal. If the sizes differ, then those numbers are not equal, and if the signs diverge, they're also not equal. Now, let's move on to the plus method. Begin with the simplest approach: keep the sign as is and add the sizes together. This approach works well for both positive and negative numbers.
00:04:59.660
However, adding a positive and a negative number gives the wrong answer. For instance, if we add positive 2 and negative 3, our computation should actually have subtracted instead of added. We need to put a conditional around this part. If the signs are the same, go ahead and add the sizes; if they are different, subtract the sizes. Now, a positive plus a negative works, as does the reverse. Let’s try another example: when we add positive 1 and negative 3, our subtraction fails when attempting to go below zero, which this version of Ruby cannot handle. We need to guard against this situation with another conditional.
00:06:30.490
To handle this, we check if the size of the first number is sufficiently large for the result to be zero or greater. If it is, we proceed with the subtraction; if not, we subtract in the opposite direction and flip the sign of the result. This approach allows us to handle situations where the result could be negative correctly, either way, be they negative or positive. Let’s perform a final check to ensure everything works properly when the result is zero. If the result of positive 3 plus negative 3 is positive 0, that seems fine, but we must address how positive and negative 0 are treated since they should be equal despite differing signs.
00:07:49.160
This part of the story is complex due to the various edge cases we need to handle. By observing that we have multiple representations, we can eliminate those special cases. Every number can have more than one representation, thus allowing us to avoid treating zero as a special case. This representation underscores a regularity that our first method lacked; this new method simplifies our operations and avoids numerous edge case considerations. Visualizing numbers as differences enhances our understanding and helps refine our operations such that we can easily bring together disparate methods for complex calculations.
00:09:03.640
Let’s now explore how we multiply using this new representation. To multiply positive 4 and positive 5, we can visualize the problem geometrically. For example, we can view positive 4 as the difference between two heights, say 2 and 6, and positive 5 as the difference between 3 and 8. Our aim is to compute how to form the product while avoiding direct subtraction. Instead, we can derive from what we have; by strategically manipulating the existing variables and calculating products without direct subtraction, we quickly arrive at the appropriate results.
00:10:44.950
Ultimately, what’s important is the shape of the code, which should ideally be clean and free of complex conditionals. This significantly eases our problem-solving process because it reduces cognitive load and potential error. The fairy tale I presented is to illustrate the concept that while the story had challenges, it also teaches us valuable lessons about representation when approaching programming problems. In the world of programming, choosing your representation wisely is crucial, as improper choices can make seemingly simple tasks much more complex. I hope this has stimulated your thinking about the implications of design decisions in real-world applications, encouraging a deeper understanding of the problem space. Thank you very much for your attention. Enjoy the rest of your conference!