Metaprogramming? Not good enough!
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 67 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/37690 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Producer | ||
Production Place | Cincinnati |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
MetaprogrammierungOnline helpWeb-DesignerComputer animationEngineering drawingUML
00:29
Programming languageTwitterGreatest elementSlide ruleFeedbackComputer animationLecture/Conference
00:57
Convex hullProgramming languageDifferent (Kate Ryan album)SoftwareWeb pageInternetworkingTime zoneProgramming languageProgrammer (hardware)BitDefault (computer science)Lecture/Conference
01:56
Monad (category theory)Erlang distributionMetaprogrammierungMacro (computer science)Programming languageProgrammer (hardware)CodeMonad (category theory)Boolean algebraErlang distributionMacro (computer science)Meeting/InterviewComputer animation
02:31
MetaprogrammierungMereologyProgramming languageLaptopMetaprogrammierungComputer animation
03:11
Inheritance (object-oriented programming)System callProgramming languageInheritance (object-oriented programming)Level (video gaming)MetaprogrammierungEndliche ModelltheorieCodeMobile appElectric generatorSystem callPattern languageSocial classLecture/ConferenceComputer animation
03:57
Object modelData structureProgramming languageEndliche ModelltheorieObject modelProgramming languageCore dumpObject-oriented programmingBitSocial classData structureInheritance (object-oriented programming)Computer animation
04:34
ImplementationProgramming languageType theoryAtomic numberRepresentation (politics)Function (mathematics)Primitive (album)Semantics (computer science)Axiom of choiceAbstractionDecision theoryInheritance (object-oriented programming)AerodynamicsMathematical analysisTablet computerProgrammer (hardware)Artificial neural networkSuite (music)Endliche ModelltheorieOpen setSoftwareObject-oriented programmingCodeExtension (kinesiology)Object modelOpen setMereologyProgramming languageCodeProxy serverPattern languageDifferent (Kate Ryan album)Revision controlBitInheritance (object-oriented programming)Parameter (computer programming)PrototypePattern matchingComputer animationLecture/Conference
05:27
State of matterSimultaneous localization and mappingSocial classPermianObject-oriented programmingInheritance (object-oriented programming)Core dumpLambda calculusDefault (computer science)Inheritance (object-oriented programming)Generic programmingCore dumpObject-oriented programmingSystem callSocial classExtension (kinesiology)1 (number)Programming languageDisk read-and-write headParameter (computer programming)Arrow of timeLevel (video gaming)State of matterCodeSampling (statistics)Pattern languageLink (knot theory)Normal (geometry)Exception handlingObject modelOnline helpDifferent (Kate Ryan album)Fitness functionBuildingBitMereologyDefault (computer science)Group actionOrder (biology)ImplementationSet (mathematics)MathematicsTrailMaxima and minimaSingle-precision floating-point formatLatent heatDiagramKey (cryptography)Network topologyInstance (computer science)Dot productPlastikkarteOcean currentFunctional programmingRight angleComputer animation
13:23
Inheritance (object-oriented programming)State of matterLambda calculusImplementationData structureDefault (computer science)RootSystem callComputer virusTablet computerImplementationLine (geometry)RoutingDefault (computer science)Object-oriented programmingSystem callSocial classState of matterBitObject modelRight angleMessage passingPoint (geometry)Network topologyCodeCycle (graph theory)Arrow of timeLink (knot theory)Data structureRootDiagramInheritance (object-oriented programming)Function (mathematics)Parameter (computer programming)CASE <Informatik>Physical systemHookingLetterpress printingCore dumpGoodness of fitLoop (music)Kernel (computing)MereologySurjective functionOnline helpGraphics tabletPointer (computer programming)Set (mathematics)Power (physics)Error messageSingle-precision floating-point formatBuildingSpacetimeInfinityMatching (graph theory)NP-hardProgram flowchart
21:19
Social classState of matterLambda calculusSystem callLimit (category theory)Execution unitInheritance (object-oriented programming)VolumenvisualisierungState of matterInheritance (object-oriented programming)LoginDefault (computer science)Object-oriented programmingRootSocial classLetterpress printingSingle-precision floating-point formatInformationCodeSystem callBitMultiplication signFunctional programmingIntercept theoremPhysical systemRevision controlParameter (computer programming)Right angleSoftware developerMessage passingLink (knot theory)Line (geometry)Endliche ModelltheorieImplementationFunction (mathematics)MereologyPoint (geometry)Cheat <Computerspiel>Object modelRoutingUniform resource locatorRun time (program lifecycle phase)Different (Kate Ryan album)MetaprogrammierungCore dumpDrop (liquid)Program flowchart
27:41
Inheritance (object-oriented programming)Lambda calculusState of matterPermianObject-oriented programmingModul <Datentyp>Normal (geometry)MathematicsMultiplicationInheritance (object-oriented programming)Social classDisk read-and-write headElectronic mailing listProgramming languageSingle-precision floating-point formatDefault (computer science)DiagramObject-oriented programmingNormal (geometry)Multiplication signImplementationObject modelDrop (liquid)RootLevel (video gaming)Sheaf (mathematics)CodeCoprocessorException handlingOrder (biology)Module (mathematics)Loop (music)SpacetimeDifferent (Kate Ryan album)Binary codeBuildingQuicksortRoutingIntercept theoremGoodness of fitRight angleVirtual machineState of matter
31:57
Physical systemSymbol tableSocial classObject-oriented programmingInheritance (object-oriented programming)Computer virusState of matterWritingBoundary value problemBit rateMultiplication signComputer programmingSlide ruleSoftware developerPhysical systemLevel (video gaming)Entire functionPrototypeConfiguration spaceEndliche ModelltheorieObject-oriented programmingPoint (geometry)MetaprogrammierungCodeLink (knot theory)Computing platformObject modelSocial classPattern matchingSampling (statistics)Touch typingBuildingElectronic mailing listAddress spaceEmailMultiplicationRow (database)Electric generatorShared memoryTwitterCloningDependent and independent variablesComputer fileInheritance (object-oriented programming)Programming languageOnline chatFitness functionConjugacy classComputer animation
34:43
Programming languageMultiplication signImplementationFunction (mathematics)Point (geometry)Macro (computer science)Meta elementInstance (computer science)Spectrum (functional analysis)MetaprogrammierungRight angleProgrammer (hardware)Lecture/Conference
36:02
Coma BerenicesXML
Transcript: English(auto-generated)
00:22
I'm Justin Weiss, and I lead the web development team at Avvo, where we get people the quality legal help they deserve. Before we get going, at the bottom of these slides, that's my Twitter name. Feel free to ping me with any questions or feedback, and I'll check it out afterward. And there's also a hashtag over there, tinyobj, free to use if you tweet about or take any pictures
00:41
or anything like that. Now I guess most of us are here because we love Ruby. I mean, this slide kind of says it all for me. But how many of you have started to wonder what your next favorite language is gonna be? Right? I mean, this isn't a totally new thing. One of the biggest things that stuck with me after reading The Pragmatic Programmer so many years ago
01:01
was this idea of learning a different language every year. So I did, and I still do. After I read that book, I decided that Ruby would be the language that I'd learn for the year. I built everything in it. Like, I felt like it was my new language. So naturally, I thought that next year's would also be my new language. But year two, I learned another language,
01:20
and I went back to Ruby. Year three, I learned another language, and I went back to Ruby. And here I am, over 10 years later, and my primary language is still Ruby. And this kind of bothers me a little bit, because I want to think of myself as open-minded. Like, I want to be more flexible than that. I don't want to identify myself by the language I write in, and I don't want to automatically default to one just because it's the one that I'm most familiar with.
01:43
But why doesn't anything else stick quite as well as Ruby did? If you think about this a little bit, you come to this question. What makes Ruby, Ruby? Why do you use it? When you think about Ruby, what comes to mind? What about programmer happiness? I mean, if Ruby was designed to make programmers happy,
02:01
I feel like it's wildly succeeded. I've never had a language fit my brain so well, or help me turn my thoughts into code so easily. And that's really cool. But there's also something else. Like, what do you think about if you think about language features? And just like you might think, Erlang equals actors, or Haskell equals monads, or Lisp equals parentheses.
02:21
Sorry, macros. With Ruby, I think about metaprogramming. So what do you think? Like, is Ruby happiness, or is it metaprogramming? Why not both? I think the metaprogramming part, in a way, leads to the happiness part. I can see by some of your faces that you don't totally agree, but I do.
02:41
With metaprogramming, you can craft the language to you and what you need to do. You aren't stuck with what the language gives you, and the language doesn't fight you. Those things, they make the language feel like a part of you, like your favorite jacket, or your laptop covered in stickers of your favorite things not just a tool that you use to get through the day.
03:00
That's how I feel metaprogramming leads to happiness. Ruby helps you feel joy by getting work done more quickly, and having a lot more fun while you do it. And in my experience, metaprogramming plays a big part of that. But what if you wanted to know what else is possible? What if you wanted to do more than what metaprogramming allows you to do? What if you wanted to change
03:21
how a language works at a deeper level? Ideas like basic inheritance, like if you, when a method is called, if it doesn't exist in your class, call it in your superclass. Those patterns are usually hard-coded into a language, and they can't really be changed easily, even if sometimes you want to skip a generation. But what if you wrote code in a language
03:42
using the same pieces you use to build that language? You'd be able to change almost anything about how the language works. You'd be able to mold the language to the ideas and to the mental models that your app uses. And you can learn about a language at a deeper level that you normally have access to. The neat thing is, we can get there.
04:00
And to do it, we'll try building a brand new object model on top of Ruby. Okay, object model is a little bit jargony. What I mean by that is the core concepts, the data structures, the methods you use to build things in your language. Do you have classes, objects, method missing, inheritance? All of these things are pieces of an object model. If a language is how it looks,
04:22
you can think of an object model as how it works. And if our object model is flexible enough, it can change itself, and that's what we're going for. So what would that flexibility look like? There's a paper I found called Open Extensible Object Models that describes how to create a flexible object model like this.
04:40
From only a few small parts. And we'll follow along, but we'll also take advantage of some of the flexibility that's already built into Ruby. So we won't follow it exactly. And all of this starts with a question. If you're given a method name, and you have some arguments, how do you know which version of the method, or which code to call?
05:01
So many interesting differences between languages, like whether it has inheritance, or pattern matching, or prototypes, or proxying. They all come from answering this question in a little bit of a different way. But what if you could describe in code how to find the right method? And what if you could change that code to act differently in different situations?
05:22
You could change the entire style of your language just by overriding a method. In a language with simple single inheritance, that lookup code might look something like this. You look up a method by name in your object. If it doesn't exist, you find it in your parent instead. But already we're starting to assume some things,
05:41
like object, class, parent. Do we really need to lock those ideas down already? What's the bare minimum that we need in order to really be an object model? It helps to think about what an object actually is. It needs to keep track of some state, like a person's name or their favorite food. And it needs some way to access or change that state,
06:02
like a set name or an eat method. Those methods make up the object's behavior. So state and behavior, those are really the two kinds of things that we need. There's a big difference between state and behavior though. You can share behavior between a set of objects, like every person object might have an eat method. And that method would run the same code
06:20
no matter who calls it. So, but state is specific to a single object. So for instance, while two people could have different favorite foods, that food could be eaten in the same way. This is an important part, and it kind of drives a lot of the rest of the stuff that comes in here, so it's good to solidify. State is unique to an object, but behavior is shared between a set of objects.
06:43
So what is behavior then? If you think of behavior as a group of methods shared by a bunch of objects, the answer starts to solidify a little bit. A group of methods is state, right? It's just a collection. So what if behavior could be an object itself? It might look something like this.
07:00
It has a set of methods that could be used by another object that points to it. You could even call this kind of object a class, like a person class, because it acts a little bit like a Ruby class. Different objects that use it can all use its methods. But if a class's state is a set of methods, what would a class's behavior be? What if you went one step further?
07:22
This is where it starts getting a little bit tricky, because it's a little bit beyond what we're used to day to day. Remember, behavior helps you access and change your state. So if you wanted to add a method to something we're treating like a class, where would that add method, method come from? How would that class know how to look up methods that are on it, or its inheritance tree?
07:42
Just like how the eat method on a person object comes from its behavior, the add method method on a class object comes from its behavior. You just follow those arrows, those links. You could think of this object, this one on the far left over there, as the class's behavior, or the class's class. This is a little bit weird,
08:00
and it gave me a headache at first. Because what is the class's behavior's behavior, and then what is the class's behavior's behavior's behavior, and so on? But eventually it all goes recursive, and you can stop thinking about it. What's cool though, is that classes, class objects, and behaviors, they're all kind of at their core the same thing. They're just state and behavior. But you start to extract these kinds of patterns
08:21
as you start playing with them, and hooking them together, and filling them out. Even though they're all the same thing though, here I'm going to describe them as a few different things. Because if you think too abstractly, this stuff is impossible to keep in your head. Trust me, I tried. So for now I'm going to call an object any of these things. A class, a thing that builds objects,
08:40
holds methods, has a parent, like one level abstracted from your objects. And a behavior, a thing that holds specific methods, like lookup and add method, that make up the core of this object model. So what do we have so far? We have a simple object, we have a way to add methods to an object state, and we have a lookup method that you can use to find methods
09:00
given a name and some arguments. And there are really only a few more things that we need. If you have a class, you need some way to create objects from it, like .new, objects that can use that class's methods. So we'll write a method for that that we'll call build object. And you'll also need to create new behaviors or subclasses so you can override methods. For now, basic inheritance is enough to get things started,
09:22
so we'll need a method to handle that. We'll name that one delegate, because after calling it, unknown methods get delegated to the thing that's linked as the parent, the class that you call delegate on. And then the last thing we need is a way to actually call methods by name. So we'll name that one send. So we have five methods, add method,
09:40
lookup, build object, delegate, and send. And one super generic object layout. And with just those parts, you have a healthy object model you can use to build in almost any language, and extend in almost any way that you can imagine. So I said we can build it in almost any language, we're at RubyConf, so let's build it in Ruby.
10:02
To keep Ruby's helpfulness as far away as possible, we'll define our object as a basic object. If you haven't seen basic object before, it's just like a normal object, except it's missing all the methods that make object useful. So we wanna keep ours small and simple, we don't wanna accidentally run into that stuff, and cheat a little bit, so this is a good fit.
10:21
And by the way, don't feel the need to remember or completely understand fully all of this code. I'll include a link with it just for all the sample code in it towards the end. So we'll need to write those core methods, those ones that I just described. Once that's done, we can use those core methods to start creating objects and hooking everything together. The first one we need is add method.
10:41
This one defines a method by adding it to a class. Remember, classes store methods in their state. So this one is pretty easy, it just adds the method implementation to the state under a methods key. For all these methods, as you can see by my beautiful ASCII art arrow up there, we pass the thing the method was called on
11:01
as the first argument. So if we're calling person.add method, or like, Justin.hello, or something like that, Justin would be passed as that first argument, and person would be passed as that first argument. The next one we'll write is the lookup function, at least the default one. This is one that's fun to override. This one's kinda big, so we're gonna take it in parts. The first thing we'll try to do
11:21
is find the method that we're trying to call in the current behavior of the current class's state. Remember, in add method, we just put it in there. Here, we're just pulling it right back out. If it can't find it, it'll go through the parents and all of the rest of its ancestors by calling itself recursively. So here's what that looks like all put together. We try to pull the method out of the behavior state,
11:41
and then if we don't find it, we call itself recursively on the parent to try to track it down somewhere in our parent hierarchy. The next one is build object. This one is the one that's kinda like .new. It creates a new object from a class or from a behavior. This creates a tiny object object, one of those ones that we just defined earlier.
12:02
It sets an empty state so we don't have to worry about initialization or anything like that, and it points the behavior to the class that we're calling build object on. So if you call person.build object, it's going to take your new object and it's going to say this is a person. It has access to those person methods and so on. The last one we'll need to build
12:21
is our delegate method, our inherit from a class, create a subclass, that kinda thing. This one is probably the trickiest out of all of them, so we'll start with a diagram. In this example, say we wanna create a baby subclass. This baby subclass needs to have access to our core methods like add method, look up, all that stuff. But how do we get that?
12:42
Maybe we need to create somehow, so we have to call build object on something to create it. But what do we call build object on? Well, just like we just saw, you call build object on a class or a behavior and it links to there so it has access to all those methods. You can see that arrow pointing over to it. So we wanna call build object on this default behavior
13:01
way up in the upper left. The problem is we don't really have direct access to that. We're not calling delegate on that default behavior, we're calling delegate on the person. But the person has a reference to that behavior, so what we wanna do, all put together, is we want to grab the default behavior from the person, call build object on that to build the baby object,
13:21
and then set up the parent link and that method's hash. And so you can see from the code up here, we do exactly that. We get the behavior from the parent class, we call build object on it, and we get our new subclass. The rest of the method is the easy part. We set the parent class in the state, we set an empty methods hash to the state so we don't have to worry about lazy initialization
13:42
or anything like that, and then we return the new subclass. And again, that's what that looks like all put together. There's a little edge case at the top that don't worry about. Again, we grab the parent class behavior, build the object from it, get the subclass, initialize it, and then return it.
14:01
So, this has been pretty fast, but we built our object structure and most of the methods we need. We still need the send method so we can call methods by name. But because we can call each of these things manually for now, we actually have enough to set up the kernel of our object model. So we'll do that. When we're done, it's gonna match this diagram.
14:21
We have our default behavior, which is gonna hold all of the default implementations of the methods that we just wrote, and we have a root object class which you can think of like object in Ruby that all other classes in the system are going to ultimately inherit from. So if you want something to be on all objects, you would add it as a method on the root object class. We'll build this step by step. The first thing we need is a thing
14:41
that we can start hooking all of our methods on, that default behavior. We'll create one by calling behavior delegate because this is going to set up that methods hash and the parent link. In this case, we don't have a parent because there are no objects in the system to inherit from, but we'll fix that later. The next thing is this default behavior needs a behavior.
15:01
You need to be able to add methods to it. You need to be able to look up methods on it, and so where is that gonna get its add method and look up methods from? Well, if it's the thing that holds those and you follow those behavior arrows to figure out which object you need to, or where your methods are, then it should just cycle back around to itself. So we'll do that by setting default behavior.behavior equals default behavior.
15:25
Next thing we'll do is we'll define the root of our object tree. Like I said, this is kinda like Ruby's object class. This one doesn't have a super class. It'll never have a super class because it's where inheritance goes to die. So we'll set its method to that, so we pass nil again.
15:41
And then it'll need access to add method and look up and all of those other methods that we're putting on the default behavior. So we'll point over to that default one that we just created. Okay, so we have a root object. We have our links to our default behavior. We have our default behavior. We can start hooking methods on too. But there's still one thing missing. I said we were gonna fix up that default behavior's parent thing later on,
16:02
so this is a good place to do it. We want our default behavior to act like an object. If we define a method on object, default behavior should be able to use it. So this is pretty simple. We'll just set it manually for now. So this is actually all the code that we just saw for hooking up our object model. It's like five lines of code. We create our default behavior.
16:21
We set it to loop back to itself, get a root object class, point it to our default behavior, and then hook that parent link back up. We're still missing those important methods though, like add method, look up, all that kind of stuff. So here we're going to use add method to attach each of those to our default behavior. So we have something that holds
16:40
those default implementations to the system. And that's pretty much it for setting up this object model. This is an example of how you might use this. So imagine that you wanted to create a greeter class. You could call delegate on the root object class that would point greeter class's behavior to the default one.
17:00
It would point the parent link up to root object class, and you have a class that acts like a class. Then if you wanted to add that hello method to greeter class, you could call add method on it. It's gonna find the add method implementation from its behavior, and it's going to add that method into its state. You could call build object on greeter class to create a greeter, which is going to then be able to use that hello method
17:21
because, again, you just follow those behavior lines to figure out where your methods are. And then you could set some state in your greeter object, which could then be used by your hello method or any other methods that are defined on that greeter class. Remember, it's all about what's shared and what's not, and that's what drives a lot of this implementation.
17:40
State is unique, so state exists on an object. Method implementations are shared, and so that's why you find them through that behavior link, because many objects can all use that behavior link to link to a single class or behavior. But we still need to be able to call those methods. You don't want to have to pass around actual objects that you then call as methods. You just want to be able to say, hey, I want to call a method named whatever.
18:02
So for that, we need to define send. Send uses a helper method called find method up there, which takes an object and a method name, and it returns the right code to call. Then if it finds it, it's going to call it with the right arguments. If it can't find it, it's going to throw an error. Pretty simple. All the complicated stuff is in find method.
18:21
Just kidding. Find method is also really easy. It calls lookup to find the right method, and this is where you get your power, because that lookup method, it could do anything. I mean, you could append in a two to the end of all your methods before you find it and look them up that way. I don't know why you would, but you certainly could. But you might have noticed a problem with this.
18:40
Find method calls object send. Object send calls find method, which means that you have an infinite loop over here. You might be able to tell from that suspicious white space that there is a little bit more to add. So if we know that we're going to call that default implementation of lookup, we'll hard code it. So in this case, we say, hey, if we're trying to find a lookup method
19:01
on the thing that forms the kernel of our object model, just call the method manually, that behavior lookup that we defined a few minutes back. Okay, we're done with setup. But before we try this out, it'll help if you get a little bit more comfortable with each of these core methods. So just to quickly review, we have add method, which adds a method to a class.
19:21
Lookup, which will find an implementation from a method name. We have build object, which, like new, it'll build an object from a class or behavior. We have delegate, which inherits from a class or behavior. And we have send, which calls a method by name on an object. Now let's try this out. The first thing we'll do is we'll call delegate
19:41
to subclass the root object. This is like in Ruby doing the less than object when you're defining a class. You can see this is a little bit, the syntax is a little bit wonky because we're calling methods manually. This is using object send to call a method by name. That method it's calling is delegate and it's calling it on the root object class. This is gonna return a subclass of the root object class, that greeter class.
20:03
The next thing we'll do is we'll add a method onto that class that'll print hello world. All of our methods take at least one parameter, which is the sender of the method. You can think of that as a way to access like self in Ruby or this in JavaScript. Actually, don't think of it as a way to access this in JavaScript because that's just gonna get confusing. So think of it as self in Ruby.
20:20
That's how you get your object state. So you can think of if you could access the object state off of that object parameter. The next thing we'll do is we'll create a greeter object from the greeter class using that, again, object send to call a method by name. It calls build object on the greeter class and that's gonna return a new greeter object that has access to all the methods that that greeter class defines.
20:41
And then we'll call the hello method on the greeter object and we get our output. So here's what we just did. We subclassed root object to create a greeter class. We added a hello method to it. We built an object from it and we called hello on that object. When we called hello, it followed that behavior line to find the hello method on greeter class and it called the implementation
21:01
and printed out our message. This code looks terrible, I know. We did a lot of work to create an object model that looks a little bit more functional than it does object-y. We've dug ourselves into a pretty deep hole of awful, awful syntax. So how are we gonna get out of it? We'll dig our way out.
21:21
What could fix bad syntax except adding a whole lot more metaprogramming? So we're gonna go straight to method missing. Luckily, it doesn't take much. This looks big. Don't try to completely get it right now. All this does is it walks through our object's ancestors looking for an object send method. Once it finds it, it calls it, passing our method name and arguments. It's just a fancy way of turning a code like this
21:43
into this much, much, much nicer code. So now, instead of calling object send, call greeter hello, we can just call greeter.hello. It's gonna make the rest of this much, much easier. Finally, we'll add object send to our root object so that all of our classes in the system
22:00
have at least one that they can fall back to. This might seem like cheating, and it is, but I figure the stuff is hard enough to understand without the syntax fighting you the whole way through. Now remember, objects can have their own different state. So let's try that out, and let's write a method that accesses the object state
22:20
to print out a different message depending on what kind of thing is in that object state. We'll create a few objects, Alice and Bob. We'll give them names, Alice and Bob, and we'll call that new method helloName. You can see helloName, even though it's the same implementation, reaches into the object state to print out different messages.
22:41
So now that we have this object model, what kind of stuff can we do with it? I mean, you can define classes and add methods in C++, and that was invented like forever ago. Seems like we've done a lot of work and just ended up making the same kind of stuff that we could always do a whole lot harder. But remember, what we're trying to get out of this is flexible method dispatch. So let's take a look at some interesting new models
23:01
that we can build on top of our basic one. What if you wanted to log all of your method calls to an object? Like, every time you make a method call, you wanna print some information about that method call. That's not that hard to do. We can write a new lookup method that uses the old lookup method to find a method,
23:22
wraps it in some function that can do anything, and then returns that function so that any method we call goes through that interceptor function on the way through. A method that wraps another method, we'll call it that interceptor method, it might look like this. You pass it a method name, a method implementation, and some arguments. It prints out some stuff,
23:41
and it calls the method as part of its implementation so that we do the work that we were planning to do. Now, if you wanna override a method on a class, you subclass it and then override the method, right? It works the same way on a behavior. If you wanna override one of those core methods, you can just subclass that default behavior, add a method implementation,
24:00
and point the new behavior to it. So now, like I said, if you follow those behavior lines, you should be able to see that the person class and anything that comes from the person class is going to use that new lookup method on our intercepting behavior to do our intercepting stuff before the method is called. Remember, you just trace those lines.
24:20
So here's what this looks like in code. First, we'll use delegate to inherit from the default behavior to create our intercepting behavior. And then, any methods that we define on that intercepting behavior are going to override anything on our default behavior for anything that points to that behavior. This is our new lookup method. Again, it's big, so we'll take it in pieces.
24:41
First, we need to be able to find our old lookup method. So we need something that acts kind of like super. So how do you find that old version? Well, if sender is our person class, like we're calling lookup on our person class, you can, once again, just kind of try to navigate that to find that lookup method on our default behavior up in the upper right over there.
25:01
So if we want the old version of that method that's centered up behavior to get to our intercepting behavior, you go through the parent link to find the lookup method to find out its parent, and it'll find it that way. So that's what that code does. You can see it grabs the super behavior from sender.behavior.state parent.
25:20
It grabs the lookup method from that super behavior, and then it calls that old lookup method to grab our original implementation. If we find a method, we'll wrap it in a new interceptor function. You define that function so you can do whatever you want in it. You can log method calls or do something totally different. Then we pass some details like the method name,
25:41
the original method, so we can call it, and the arguments, so we can call it, and then we'll create a person class that will use that new behavior. Just like in Ruby, our person class inherits from the root object class. Again, this is like saying person less than object.
26:02
Next, we'll manually point the person class to our new intercepting behavior. This is kind of cool, so take a look at what's happening here. With a single line of code, we've changed how method lookup works for anything created by that class. You see, like, use the old method, use the new method. Use the old method, use the new method.
26:20
Use the old method, use the new method. So here's that logging function again. Same thing as before. In the last line, we make it the interceptor method for that class. We'll define a few methods on this class, name and location, so we can test it out. And then we'll create a person object using build object on the person class, and call those two methods.
26:41
There's our logging. Now what happens if you reset the intercepting class's behavior back to the default one at this point? Check it out, logging goes off. So it's not only you can change fundamental ways of how methods are found and called, you can change it on the fly. You can change it at run time,
27:00
just by pointing to a different behavior. One more quick intercepting example. What if you have a method that sometimes randomly fails? I mean, I know nobody in this room has that problem, but I hear of developers that have this problem and methods randomly failing sometimes. So here's one that fails two thirds of the time. What if you could automatically retry this method until it succeeds? Here's an interceptor function that does just that.
27:22
It's going to try to call the method. Every time it returns false, which we'll consider a failure, it's going to sleep a little bit until the method succeeds. What happens when we try it? Hey, it worked, eventually. Okay, now we're gonna move on to something completely different. But before we do,
27:41
let's take a second to let this intercepting method stuff drop out of our heads so that we're ready for the next one. Take a sec. All good? Let's go. Last example. We're gonna build multiple inheritance, where a class can have more than one parent class. If you can't find a method on your class, it'll search all of the parents
28:00
to be able to find a method implementation. So let's draw this out again. I always find it easiest to start with diagrams. We'll start with this object. In this example, the very well-named Justin object. This object has a class, and it's this class that'll have multiple parents. So how would you build something like that? Our class with multiple parents will look just like a normal class.
28:21
It still has the methods hash in its state. But instead of a parent class, it has a parents array. Those parents could be anything. They could be normal classes, or interceptor classes, or basically whatever. But in order to get those multiple parents, we need to be able to add parents to that parent list. So where is it gonna get that?
28:41
I mean, we said that any of these objects are going to be able to get their methods from their behavior, but there's no add parent behavior, except for that suspicious-looking blank space in the lower right-hand corner, which I'm going to fill. So since methods come from a behavior, we need a behavior that can do two things. We need it to be able to add a parent to that class, and we need a way to be able to look up a method
29:02
from all parents in that class, which our original implementation doesn't do. So we'll write an add parent method, and we'll override the lookup method with a new one. Once again, you subclass the default behavior to get your new one. And then we'll write the add parent method. This one looks big, but that whole first section there is just for initializing it
29:20
if we don't already have a parents array. At the end, it appends the new parent to that parents array. So you'll end up with your original parent plus a new one, and then any time you call this afterwards, you're just going to end up appending that new one to the end of the array. And now here's the new lookup method. This is almost exactly the same as our original one, with just one change.
29:40
Instead of looking up methods in a single parent, it's going to loop through all the parents, and it's gonna break when it finds the first one. So let's try this out. Here's two normal classes, a person class and a greeter class. Nothing special about them, they just inherit from our root object the same as anything else would. And then we're going to define name on the person class and greeting on the greeter class.
30:03
And here's a class, up the code at the top, that inherits from both person and greeter. To get that add parent method at the end there, we need to point the behavior to the new one, otherwise that method isn't going to exist. So this inherits from the person class, and then it adds the greeter class as a second parent to the social person class.
30:24
And now if we try it out, it fails! No, just kidding. It gets both the name and the greeting method from both of its parents. So it gets the name method from the person method and the ancestor, and the greeting method from the greeter ancestor. So this is a lot of flexibility. We built three different styles of objects,
30:41
basic single inheritance, two kinds of method interception, and multiple inheritance. And it all came from a class and five methods. Lookup, add method, build object, delegate, and send, and our little tiny object. And each piece of it is simple enough that you could build it in almost any language, from C all the way to Ruby.
31:01
But Ruby's also a really flexible language. I mean, you could write a lot of these examples straight in Ruby, using things like modules and method missing and basic objects. So what's the difference between this object model and Ruby's? I see it as intent. Just like how Ruby makes metaprogramming a normal thing to do, this object model
31:20
makes changing things at a deeper level a totally normal thing to do. And even though all of this eventually becomes a bunch of binary code running on a processor on our machines, the language you use and what's normal inside it, it changes your brain. It makes some things more natural and other things more difficult. That's why I love playing like this. That's why I love learning new languages,
31:41
even if they don't stick with me. These experiments, they help you think in a totally different way. You try on a new way to see your code, and you keep what works, and you drop what doesn't. And so after these examples, as I can see some of you looking at me like, why? Like, why would you do something like this? It's that. How many of you learn best when you break something? You know, when you test the boundary,
32:01
when you're not sure that it's gonna work, when you're pretty sure that it's not going to work? I strongly believe that the best way to understand a system is to test it, to push it, to break it. And metaprogramming is a fantastic way to break all of the systems that you depend on. But when you push the boundaries, you'll be more confident with the language.
32:20
You'll understand at a deeper level how it all works. So try writing code that might seem weird or irrational. Because who knows, you'll probably learn something new. And at the very least, at least you'll have a good story to tell. It could be a mess of metaprogramming spaghetti code that would generate active record models on the fly based on entries in a config file. That was a bad idea.
32:41
Don't do that. But it could be an entire object model built from five methods in a class that helps you understand how object-oriented programming actually works. And when you learn something like that, when you learn a tool that you can use to investigate more tools, that's a platform you can use to learn new things by building them, like building multiple inheritance,
33:00
or building prototype object cloning, or building pattern matching. And when that happens, not only do you learn new stuff, but you also increase your rate of learning new stuff. And I really can't think of any better ways to become a better developer. So what I'm asking of you is to give this a try. Take a look at the sample code that I'm about to give you and try to understand it.
33:21
Not by reading, but by playing with it, by trying to maybe re-implement one of these examples or take it a step further or build something totally new. And if you're anything like me, as I was several times when putting this together, you're gonna fail miserably a few times. I failed way more than a few times. But eventually it'll click and you'll get it. And when you're done, you'll understand a lot more
33:41
about metaprogramming, syncing classes, like these deep levels of object models that you never understood before. And so if you wanna get in touch about that, or wanna talk programming, or really anything else, I would love to talk with you. My email address is up there, use it. I read and respond to everything. And then if you're ever in Seattle, let me know. I would love to grab a copy with you in chat programming.
34:01
I'm also on Twitter, at Justin Weiss, where I share programming and other tech-related articles. And that last link points to a list of resources for this talk. It's an important one, you can think of it as like the show notes. It has a gist for the sample code with some stuff I couldn't fit in, like prototype object cloning. And it also includes like the slides for the talk
34:20
and some other articles that are kind of in the same vein. And I know I have some time, I think about 10 minutes, so I'm happy to answer any questions or go deeper into any of this stuff, as you're interested.
34:46
Yeah, the first time I thought about this, so the question was have I thought about using things like define method and method missing to try to override Ruby's implementation with something more flexible like this, right? And when I first read the paper, I was so fascinated by this that I decided that I was going to try that.
35:02
And then I had second thoughts when I realized just how awful the performance is likely going to be, if it's not built in to the language. So the question was how do other languages compare with Ruby and metaprogrammability? There's a huge spectrum, and it's also hard to tell because it's not until you really start to get deep into a language that you really start to make use of that.
35:23
Like for instance, my language for this year is Elixir, and Elixir has a lot of heavy macro support, which can be used for some really interesting metaprogramming stuff, but I haven't got to a point where I've been using it enough to really be able to have a solid opinion on it. Definitely, I think the most metaprogramming-ish languages
35:41
that I've run into recently have been, or I mean over those years, have been surprisingly Objective-C. I actually tweaked this talk using some of that. And LISP. But yeah, those are probably the two biggest. All right, well thank you once again for the time. Thank you.