00:00:12.240
How are you all doing today? Good? You wrapped up? You know the difference between a keynote talk and a regular one is that I don't have to cut out all my material to get down to 30 minutes.
00:00:17.279
I'm here to talk to you today. This is essentially like a talk about what I did over my summer vacation, except this is more about last fall rather than over the summer. But before we get into that, I want to talk about this guy right here.
00:00:31.199
He was a physicist and did a lot of work in the early 1900s on the development of the atom and our understanding of how the atom is put together. He is a contemporary of Einstein; in fact, he and Einstein had a series of debates on quantum mechanics. The summary of that is that Niels Bohr was correct and Einstein was wrong. So can you imagine that? Back in the early 1900s, physicists had discovered that the atom was not a concrete little ball of something; it was actually made up of several parts.
00:01:04.720
It had a very dense nucleus that was positively charged, and it had a bunch of electrons that somehow orbited around that in an electron cloud that was attracted to the nucleus because the electrons were negatively charged. The nucleus was positively charged, and there was a very strong electromagnetic attraction between them. But what they didn't understand was how that electron actually maintained its orbit.
00:01:41.280
You see, they had in their minds something like the planetary model, where the nucleus was at the center, and the electron was spinning around that nucleus very quickly. Now, they knew that if you take a charged particle and move it back and forth really fast, it generates an electric field. An electric field takes energy to generate, so if the electron was orbiting the nucleus really fast, then it had to be giving off radiation. Because it was losing energy, it would eventually spiral in and collide with the nucleus.
00:02:06.479
So what was keeping the atom apart? Well, Bohr thought about this. I've been told that he had this in a dream. Although I tried to look it up on our source of all knowledge, Wikipedia, and it didn't say anything about this, but I understand that he saw this in a dream. The explanation for that, which we'll get to in a second, involves a really interesting phenomenon.
00:02:44.080
If you take any material and heat it up until it glows, it gives off radiation. Now, not just general radiation, but the radiation comes in spikes. So you have a very strong spike here in the red, then yellow, green, blue, and different materials have entirely different signatures for how they generate these patterns. No one knew why everything was different or even why they gave off this particular spectral pattern.
00:03:13.920
So they took a look at hydrogen and noticed that it had a very simple pattern, with one line down here in the red and another line up here kind of in the greenish blues, then a blue, and then up into the ultraviolet range. These were very set distances apart. The differences in frequency correlate with differences in energy.
00:03:43.920
So Bohr had the idea: what if the electrons orbiting a nucleus could only exist in certain energy states? Thus, when an electron moved from one state to a lower state, it would have to give up energy. The states closer to the nucleus would be lower energy states, while those farther away would be higher energy states. As it moved closer, it would lose energy and emit a particle of light. Because the different states were discrete, it could only move from one state to another and only emit very distinct patterns of light.
00:04:32.000
Here, as we move from energy state one to two, we would emit or absorb a red line of light energy; between two and three was yellow, three and four was green, and so on. Each energy level had its own quanta of light energy. This was the very beginning of the idea of quantum mechanics, the physics of the very small and very fast, and Bohr was a leader in that thought. However, this idea was like mind-boggling; no one had thought of this before.
00:05:08.080
It's an example of what I call lateral thinking. Thinking outside the box when given a problem can help you solve it in a way that no one else has thought of before. Can you think outside the conventions and do something different? That's what Bohr did in solving the question of how electrons stay in orbit around a nucleus.
00:05:39.840
There was an interesting question raised because of that: if moving between orbits in an atom releases a particle of light, what's very interesting is that scientists at that point believed that light was actually a wave, and they had good reason to believe that as well.
00:06:00.240
If you take a wave source like light or sound, or even waves in the ocean, and you force them to go through two slits like this, you will get different patterns. If I cover up slit number two, I would get one intensity graph; if I cover up slit number one, I would get another intensity graph. However, if I leave them both open, I don't get the sum of the two intensities. Instead, I get something entirely different, because the waves interfere with each other.
00:06:44.560
As one wave reaches its trough, the other one reaches its peak, and they tend to cancel each other out in a pattern that looks like the graph on the far right. For a double-slit interference pattern, it is clear that it exhibits wave behavior.
00:07:11.440
So, is light a particle or is it a wave? Is it more particle-like, as some believe, or wave-like, as the double-slit experiment shows? Now, what if you took instead of a wave, single particles like electrons and shot them through a double slit?
00:07:39.760
Well, if they were particles and you shot one at a time, and did it repeatedly over and over again, you would expect to get two lines – one for each slit. They shouldn’t interfere with each other because you're sending one at a time, so it either goes through this slit or through that slit; you should get two patterns.
00:08:06.160
So, let's take electrons, shine them through a double slit, and you get this. Well, doggone it! That looks like wave interference. You get this even if you slow the electrons down so they go through one at a time. Instead of getting particle behavior, as we believe electrons to be, we get wave-like interference behavior.
00:08:39.839
Bizarre, isn't it? In fact, if you take electrons and shine them through a crystal, you get this marvelous pattern. We can tell the structure of crystals mainly through electron diffraction by shooting electrons through them and seeing how they scatter and interfere with each other.
00:09:04.880
But we have this whole wave versus particle duality: Are electrons particles? Well, we certainly thought they were, but they act like waves. Are light waves? No, they act like particles sometimes. So when you get to the realm of the very small, there's this strange duality going on.
00:09:30.000
At one point in time, this was our model of the atom. Bohr taught us that electrons existed in separate orbits, so we improved our model of the atom. Today, we understand the atom a little bit differently. We now understand that the atom is a series of probability waves.
00:09:59.040
Now, which of these pictures is true? Is it this one, this one, or that one? The answer is none of these. These are models, and models are neither correct nor incorrect; they are either useful or not useful.
00:10:26.480
If a model is useful for understanding something, then that's a good model. If the model gets in your way and prevents you from using it effectively, then it's a bad model. For a lot of purposes, this is a perfectly good model of the atom.
00:10:48.320
For other purposes, for deeper understanding, you might need to use this model. So models are important. Models come from abstractions, which are a good thing; abstractions are neither correct nor incorrect; they are either useful or not useful depending on what you want to do with them.
00:11:05.840
So, three things I want you to keep in mind for the rest of this talk: Number one: lateral thinking. Think outside the box; think of things that people haven't thought of before. Number two: dualities are cool and useful, so embrace them when you can. Number three: abstractions are good and powerful things.
00:11:32.960
Now we'll get to the meat of my talk. It all started last summer at the Software Craftsmanship Conference in Chicago that I attended. At that conference, I heard something I call the rule of three. If I hear something mentioned three times in quick succession, it means it's probably something I should look at and investigate.
00:11:51.240
During that time, I heard Bob Martin of Object Mentor mention the structures and principles of computer programs, and I heard Michael Feathers mention the same text. There was a third person who I now forget, but they also mentioned this book called 'The Wizard Book' for obvious reasons. This is an introductory text used at MIT for beginning programmers.
00:12:20.800
The recommendation was that all developers should check it out and see what it has for them. I thought to myself, an introductory book? I've been programming for at least as long as Josh now; what could this possibly have of use for me? We know it's an introductory book because the very first exercise is: please evaluate these expressions.
00:12:57.680
For example: five plus three plus four, nine minus one, six divided by two. So very introductory! We also know it's an MIT book because this is the last exercise: 'Produce the Scheme interpreter written in C.' Yeah, okay? Remember, this is the introductory text for MIT Computer Science.
00:13:27.760
Mind-boggling! I sat down and I decided I was going to pick up this book, and several other people at the conference decided they would do it, too. We started a Google Groups email list where we talked about it, and we also set up several online meeting times so we could get together via Skype and screen share.
00:13:59.520
Every week, we talked about the different sections of the book as we read through it together. A lot of people have done this, and if you haven't tried it, I encourage you to give it a shot. We got through chapter two and were starting chapter three, which is where it gets interesting. Unfortunately, our group kind of fell apart, so I will share with you the things from chapters one and two and let you imagine the things from chapters three, four, and five.
00:14:32.000
The examples I’ll share will be in Ruby, but they'll be in very non-idiomatic Ruby. Something bizarre and weird that you probably haven’t seen much of. So, let’s start with this: This is from the first chapter of the book; it's a square root function that calculates a square root given any non-negative number.
00:15:09.440
It does so by a series of approximations. It starts off with a guess, and then says: 'Now, while my guess is not good enough,' if you take that guess and square it, multiply it by itself, and subtract the original number from that, you'll get the difference between the square of your guess and the answer.
00:15:59.520
When that difference is less than some arbitrary limit, we say, 'Hey, we're done!' and return the guess as the square root answer. So we can get arbitrarily close to the actual value. While our guess is wrong, we need a way to come up with a new guess that's better than the one we have. If the square root times itself equals x, and if we take x and divide it by the guess of the square root, we're going to come up with another number that's different from our guess. If we average our guess with the number divided by our guess together, we come up with a new guess.
00:16:55.440
For example, if you run the square root of 100, you’ll find that in the first iteration, our guess is one. N divided by guess is 100, so the new guess will be the average of those two numbers: 50.5. In the next iteration, our guess is 50, and the new guess is almost 2. Our next guess is 26, then 15, then 10, and then 10.03, and then 10.005. So you see that we're zeroing in on 10, which, fortunately for us, happens to be the square root of 100.
00:17:38.640
Now, this is called the Newton approximation method for square roots, and it converges quickly, meaning it doesn't need many iterations to get a good guess. Let’s look at this. We can break this code down in several different ways. First, we ask the question: is the guess good enough? We could make that a method and call it ‘good enough’. So we’ll write a method called ‘good enough’; you give it the guess and the original number. While it’s not good enough, we continue in the loop.
00:18:39.520
The second piece we could look at is called ‘improve the guess’. Let's replace that logic with a method called ‘improve the guess’. Now, this is a very interesting piece of code. The important aspect here is where the square root logic is no longer tied to finding a square root; it’s now separated into two functions. Thus, it becomes a general approximation loop that can approximate anything we want.
00:19:23.040
A root of anything: not just square roots, but cube roots or any polynomial variable, just by plugging in the correct values for ‘good enough’ and ‘improve guess’. Let’s do something interesting: Let’s parameterize this! Let’s define ‘find root’, which is a general root-finding method that takes a number. We’ll pass in the good enough and the improve guess functions as parameters to this function.
00:20:33.920
We’ve pulled out the square root nature of it; we’ve made it a general-purpose function. Now we can find any root just by passing in the right values for these. Since these are now functions, we’re going to call them using the function calling syntax. This is a Ruby 1.9 idiom right here. If these things are essentially lambdas, you can do dot-call on them, but we’ll use the Ruby 1.9 syntax, which is a little shorter now to use ‘find root’.
00:21:39.680
Oh! I should mention that so ‘good enough’ and ‘improve guess’, instead of being methods, become lambda objects, and we’re again using the Ruby 1.9 stabbing proc syntax.
00:22:01.920
How many people here use Ruby 1.9 regularly? Excellent! It’s time to move, guys! It really is! Stabbing procs allow you to pass in parameters and do the normal default value things and the *args things that you can with normal methods; you can do these with lambdas now using stabbing proc notation.
00:22:59.840
This creates a procedure object, a function, which we assign to square root, good enough, and square root improve guess. Then, we can call find root with 100 and pass in the two methods that we need to find the square root of. And this will work. We could run this, and it gives us the same exact answer as our previous code.
00:23:30.080
That's neat! But it’s awkward to call, though; I wouldn't want to have to pass these two functions around every single time I call this. So, find root is a method that finds a root. What would be really useful instead of finding a root would be if I could construct a function that finds a root for me. Instead of passing in ‘good enough’ and ‘improve guess’ every single time, I’d rename our function to make find root.
00:24:17.760
So this is a function that returns another function. The function it returns will be this find root function, essentially. But we do this by passing in ‘good enough’ and ‘improve guess’. Within the lambda, we bind ‘good enough’ and ‘improve guess’ to their usages. What we get back from this function is not a root; it’s a function that calculates a number.
00:25:03.200
We have constructed, on the fly, a function that will find the root for us given the proper ‘good enough’ and ‘improve guess’ methods. That’s kind of mind-boggling, guys! Just think about that! We are building functions on the fly!
00:25:54.560
We would use it like this: we have a square root function object here, a variable, which we assign to the return value of make finders, passing square root, good enough, and square root improve guess. Then we can call square root on 100 just like we did before.
00:26:12.960
Now we’ve got the nice convenience of this, but we’ve wrapped the whole thing into function-building procedures. That’s cool! Now, this is easy for square root because we know how to calculate whether it's good enough, and we know how to calculate the improved guess.
00:26:43.520
But what if we wanted to construct other arbitrary root finders? What if we wanted to find the cube root of a number, or the fourth root of a number, or find the solution to the quadratic formula? We need to come up with a way of specifying these two functions.
00:27:10.800
Wouldn't it be nice if we could take the function we’re trying to find the root of and pass it into a function that says ‘make good enough’? It would create the good enough function and create the improve guess function for us.
00:27:59.920
So we give it the square function, and it provides the good enough and next guess function. However, that's something you would have to write yourself. If you read the book, it will tell you how to write the code that goes in those placeholders. That's a little too mathematically deep for my talk right now; I understood it when I read it, but I'm not sure I could explain it to anybody yet. It's really fascinating stuff, but we're using functional abstractions to build up more complex layers of finding this stuff.
00:28:58.240
In the end, we can just say things like 'make a root finder for us for some arbitrary function and use it.' That's magic! Absolute magic! The key thing about this is that this is bizarre for Ruby programmers, Java programmers, or even Python programmers, who are not used to composing functions in this manner. This is an example of lateral thinking for us Rubyists.
00:30:14.160
We don't normally write code like this, but many people here are JavaScript programmers. This is not that uncommon. JavaScript has a very functional flair to it for building these things. In JavaScript, you tend to do a bit of this building up things and using closures a little bit more.
00:30:46.320
When you're doing Ruby, embrace the differences. Embrace the differences; it’s good for you! So another thing I learned from chapter one is that functional abstractions are really, really powerful. You can do a lot more than you think of if you come from a non-functional language. You think, 'Oh, a functional language is just about calling functions.' No, it's not about that; it's about creating functions that do particular things, a really powerful abstraction.
00:31:20.080
That was chapter one. Now let's move on to chapter two. Chapter two is about data structures. Where chapter one was all about building functional abstractions, chapter two is about building data abstractions. They used a very simple concept to base all their data abstractions on: the idea of a cons cell, or a pair.
00:32:08.320
You take two things and cons them together, resulting in a cons cell that points to the first and second item. These can be arbitrary. They can be numbers, or in this example, they could be other cons cells, allowing for complexity.
00:32:37.760
You also have two functions called car and cdr that, given a cons cell, return either the first or the second item. With these three operations, we can build any data structure we want. If you take 3 and cons it onto a 2, you get the list [2, 3].
00:33:18.080
Cons builds the list, car returns its head, and cdr gives you the tail. So now we can build up and tear down using these two functions.
00:33:50.640
If we were to do this in Ruby, this might be an obvious implementation. We’ll create a list data structure that takes a head and tail, constructing a new struct object. The cons function then creates a new list object with the head and tail, where the car function returns the head, and the cdr function returns the tail.
00:34:39.680
This can be replicated easily in Ruby. We’re also writing a couple of support methods, for example, to take an array and turn it into a list, which is just a convenience. We can now print out lists, take the cars and cdrs, and chain those actions together. When we run it, we get these answers.
00:35:30.240
You can see that we display the entire list. We get the list exactly right as the first element, while the cdr is everything after the first element. The car and cdr operations show us these respective values.
00:36:18.560
Okay, time for lists. We’re building our basic data structure with something in Ruby that’s actually quite complex. What if we didn’t have classes to work with? What if I didn’t have the ability to build a class in Ruby? What could I use instead? Look at this code for a second.
00:36:52.800
What if cons, rather than returning a data object, returned a procedure? What if car and cdr, instead of getting the head and tail of that data object, called that procedure with either an 'h' or 't' symbol?
00:37:36.800
By building a list like this, we return a procedure that takes a single argument. When I call car on that, it will say, 'Is the parameter equal to the head? If so, return h; otherwise, return the tail.' So I have something that is functionally equivalent to our list data structure without using a data structure at all.
00:38:29.600
I have built a data structure out of functional abstraction. Let’s run this same code, but instead of using our struct as a list, we’ll use the proc as a list. When we see it working, we recognize we don't even need data structures to build the cons cell at first.
00:39:10.240
That means we can build arbitrarily complex data structures using nothing more than functional abstractions! This approach frees us from implementation details, which is really interesting. We’re dealing with abstractions, the abstract idea of a pair of things and being able to build that pair up and tear it apart.
00:39:44.240
But the actual implementation, whether we're using a struct, whether we're using procs, or whether we’re using the address register and the decrement register – which, by the way, were where the names car and cdr came from – doesn’t matter. It doesn’t matter how that pair is represented.
00:40:17.680
We also have this cool code versus data duality going on. Code can be data, because we're using code to generate a cons cell. And data can be code; this is very apparent when you're programming lists. The code is operating on the data, returning pieces of code.
00:41:07.680
List, code, data, data code – it’s all intermingled. Again, a duality that we should embrace. We can do a little bit of that in Ruby, but not as clearly as we can with lists.
00:41:37.040
More cool chapter two things we have: complex numbers in Ruby. But what if we were to implement them from scratch? We would start with a function called make complex that takes a real part and imaginary part, consing them together. Again, it doesn’t matter whether we use the procedure version of cons or the data structure version.
00:42:17.440
The re function pulls out the real piece, while the im function pulls out the imaginary part of our complex number. Now we can start writing things like complex add and complex subtract that use the abstractions of a number having a real part and imaginary part.
00:42:55.760
To add a complex number, we add the two real pieces together, and the imaginary pieces together. Likewise, subtraction works in a very similar way. We create two complex numbers and print them out, then print their addition to see it working.
00:43:42.720
Now, what are the limitations of this particular implementation of complex numbers? Number one: this function assumes we have implemented the complex number in a particular way. Now we’re using cons cells, but we can implement the cons cell any way we want.
00:44:22.160
However, if we wanted to implement polar complex numbers, where we stored the angle and the magnitude of the number rather than the real and imaginary part, we would have problems here because we're pulling the pieces out explicitly.
00:45:03.680
You couldn't mix a polar number with a rectangular complex number, because their implementations would clash. For instance, the code for pulling out the real parts would yield something different if given a polar version.
00:45:36.320
If I were doing this in Ruby, the answer would be obvious; I would create a class and implement some accessors called re and im, initializing the number with those parts just like this. Now, if I wanted to do a polar number, I would initialize it with magnitude and angle.
00:46:21.920
Yet the key part is that it now has re and im in it, so I can write code that uses both without worrying whether I’m using a polar or a rectangular complex number.
00:47:01.440
What’s this called in Ruby? This is duck typing, polymorphism, or something, as we take advantage of the OO nature of Ruby for flexible implementations. The functional version wouldn’t work because it’s always assumed a rectangular implementation of complex numbers.
00:47:40.640
So if I were to do something similar using functional techniques, my complex number would be a function. The make complex function would return a function of one argument, and when I want to find the real part, I ask the complex number for its real part.
00:48:37.680
I pass the symbol in and it looks it up. This is a hash in Ruby 1.9. If I pass it the symbol re, it looks up the value and returns it. Similarly, if I want the imaginary part, I look up im and return that value.
00:49:17.680
The polar version is a wee bit more complex but does similar things: we return a function that takes a method as an argument, looks it up in a hash. Instead of values in the hash, we have functions, and so we call the functions immediately.
00:49:51.760
So when I ask for the real part of a polar complex number, it calculates the real part for me. I’ve implemented polymorphism in a functional language by using functional abstractions.
00:50:28.160
So I can create a complex number and a polar complex number. This number here is the magnitude and angle of what was our other example: 3 and 4. So when we print it out, we get 1 and 2 or 3 and 4.
00:51:29.920
Now, the sum of the two by using functional abstractions for polymorphism we can gain benefits that we would have gotten with OO. The cool thing about this example, and about the book in general, is that when we do abstractions in Ruby, we tend to do two-level abstractions.
00:52:10.960
We implement our class here, and then we have code that uses that implementation, which works. That's fine. The book tends towards a three-layer level of abstractions, where at the lowest level, we implement basic features, such as getting the real part and imaginary part.
00:52:44.160
The second layer uses only the base functions below it, and everything else is built on top. Ruby developers tend to combine these two levels into a single implementation, not always, but generally.
00:53:17.760
But this book really called out these two features. The cool thing about a three-level implementation is that I can easily change one implementation layer and not have to rewrite much of the secondary implementations.
00:53:58.720
That is really nice; I really like that. Okay, let’s summarize. We have this whole object versus functional duality going on. You can implement objects with closures, as we saw here. Ruby closures, lambdas, and proc objects are just objects, but they're closures as well.
00:54:36.240
Chapter three is where I’m stuck right now, and I haven’t had time to get back into it. Chapter three focuses on assignment statements. You realize when you reach chapter three and you're reading through this book that you haven't used a single assignment statement.
00:55:22.320
Everything you've done up to this point has involved designs around recursion and variable binding. You don't even miss assignments until you get out of chapter three and realize you haven’t used any assignment statements yet.
00:55:57.920
Chapter three is about learning how to do assignments, which seems odd that it doesn't come until chapter three. Chapter three is cool stuff. Chapter four focuses a lot on meta-programming and building meta-interpreters, and chapter five dives into VM implementations for running various versions of the Scheme programming language.
00:56:34.080
Summary: Number one, I want you to take away from this talk the idea of using functional art to look for non-traditional solutions to your problems. If you're doing something and it hurts or doesn’t sing, take a step back and see if this is the best way to go about this problem.
00:57:14.080
Look for non-traditional solutions. Secondly, find good abstractions. I also think about the Rails 3 talk that discussed how Active Model and Active Record are evolving to better abstractions. My belief is that we are constantly seeking the useful versions of those abstractions in our libraries.
00:57:45.920
I’m a big fan of test-driven development. I think by writing your tests and using your code as a client through those tests, you’ll find the structures that help you discover better abstractions rather than sticking with the first ideas that come to mind.
00:58:30.640
Finally, embrace duality. There are things we cannot describe in a singular precise manner; sometimes they act like one way and sometimes like another. Recognizing duality is a wonderful thing; it’s a fun thing to embrace.
00:59:15.680
Resources for this: the whole structure and interpretation of computer programs (SICP) is available online in HTML, PDF, and EPUB formats. If you want to put this on your iPad or Kindle, that’s pretty easy to do.
00:59:49.920
Our study group is ‘The Wizard Book’ study on Google Groups. It's not very active right now, but if you want to join and stir something up, I'm sure someone will be glad to talk to you.
01:00:15.920
This whole presentation is available on GitHub, where you can check it out along with all the source code I used in my examples, and a lot of source code that didn’t make it to the presentation.
01:00:38.720
And that’s it! I think we’re doing okay for questions, so please find the microphone in the center aisle if you want to line up for questions.
01:00:58.080
And we can also take some people with questions.
01:01:05.520
That was a great talk! This question isn’t really going to be about it, but I’m wondering if you share my opinion about this ugly aspect of the period.
01:01:34.640
Yes, I do find that it is ugly, but I think being able to call them with just parentheses would add too many ambiguities to the rest of the language.
01:02:14.400
It would actually be hard to do and be inconsistent because nothing else in the language is invoked with merely parentheses.
01:02:44.960
That would be the only instance where parentheses would cause invocation, and thus it wouldn’t fit in with the rest of the language.
01:03:24.080
With other methods in the language, the invocation is caused by a dot rather than by just parentheses, which keeps things consistent.
01:03:55.920
You can call methods with parenthesis, but this does not cause invocation. It creates a situation where the parentheses are not requiring … they do not cause invocation.
01:04:19.680
Hello, Jim! I like the two versus three-level abstraction discussion, and I want to bring up a couple of examples.
01:04:59.200
That's good! So in that example, each is your base method, and you implement on top of your base method the inject, map, collect, select, and other innumerable methods. They're implemented on the top of a base method, which is each. Yes, there are indeed examples of three-level implementation in Ruby, but often we don't necessarily think like that.
01:05:35.680
This was an excellent talk! I think it's important for Ruby developers and optimistic developers to better understand how Scheme and Lisp work, as well as their fun aspects.
01:06:18.640
I don't want to turn this into a Ruby 1.9 bashing thing, but I have the same question as the first asker about the stacking process. I don’t understand why I would prefer the syntax closer to Perl.
01:07:11.920
That's okay – a slightly different question than the first aspect raised, but I think Ruby's proc versus lambda versus stabbing proc thing is out of control.
01:07:53.920
We have stabby procs, proc.new, and lambdas; how do they differ? Tell me quickly what's the difference between a lambda and proc!