I've seen this question come up several times since the announcement of the compiler feature. And I've seen several valiant attempts at answering the question in a general case. Generally, these attempts fall into one of two categories:
1) If you have to ask, you probably don't need it
2) When you need it, you'll know
Very deep, eh? ;-) Well, I'm going to take a stab at giving a general and specific response. But I have to admit that I also agree with #1 and #2 above. I'm simply explaining away the corollary question of: "but if I won't need this, why did RS add it?"
Let me start with the last question first: why did we add Introspection support? The short answer is: because we needed it. Of course, it certainly helps that it was the single most-requested item in the feedback system! But at the end of the day, it turns out that we required it in order to provide more powerful features to the user. So now that we have that question answered, I can focus on: why do you need introspection?
I've given examples previously about serialization and unserialization, among other things. And those are very valid uses for introspection. But the more general answer is: because it provides a powerful mechanism that allows programmers to accomplish things they couldn't otherwise do previously (as easily). That's the main reason *any* new compiler feature comes along. Compilers are meant to take the repetitive tasks and abstract them away so that the programmer can focus on accomplishing their goals.
For instance -- why have a for loop, or while loop? After all, they can be accomplished by just using a counter/controller and a goto! The reason is: because it's easier, cleaner and more maintainable!
The same is true of any compiler feature, and introspection is no different. You could serialize and unserialize things before -- you just had to write the code to accomplish them. Same for cloning, or any of the other introspection-friendly features people discuss. The difference is, there's now less code to write.
Introspection gives you a generic way to interrogate an object for its data, and manipulate the object's state. This is a powerful new technique -- but it's also dangerous. So most people tell you "omg, zounds! don't use it!" and they're partially right. Introspection can be a very fragile way to accomplish the same goals that could be accomplished without it. For instance, calling someObject.SomeMethod is less fragile than looking up a method named "SomeMethod" on an instance of someObject and calling Invoke on it. Why? Well, for starters -- just calling the method takes less code! But it's also less fragile because you can rename SomeMethod and get a compile error if you've forgotten to rename its usage everywhere. But with introspection, all of the work happens at runtime -- no compile errors! And errors that happen at runtime are just a fancy name for bugs.
So if you can do all of these things before, and this is so scary and bad to use -- why would RS (or anyone) ever want to make use of it? Because sometimes there's no way around generic programming when you're designing a toolkit.
Let's say you want to have a generic base class that performs a bunch of work on behalf of a subclass. The base class does not have access to the subclass' properties -- so if the base class wants to assign to those properties, it has to come up with some mechanism to have the subclass do the assignment. This gets pretty complicated very quickly. It's also a terrible example to try to talk out in this fashion, so let me get a bit more specific with a real world example:
Let's say that we (REAL Software) want to expose an object model that represents a database to our users. We will generate a class that represents a table, and inside of that class are a bunch of properties that represent the columns. However, we don't want to shove a whole bunch of source code into these generates classes. If we do that, then it suddenly becomes very difficult (if not impossible) to push bug fixes out to users with any of that source code's logic. Instead, we want the logic to live in a base class that we control and that isn't exposed. By doing this, we can fix the bug in one place (if any crop up, of course) and everyone benefits. This also makes the classes we generate a lot cleaner, and less scary.
Now comes the tricky part -- these classes have properties in them that represent the data. So we need a generic way to map the data from the database query onto the concrete class from within the base class. This is where introspection comes in handy -- the base class can do something like this:
dim ti as Introspection.TypeInfo = Introspection.GetType( self )
dim props() as Introspection.PropertyInfo = ti.GetProperties
for each prop as Introspection.PropertyInfo in props
if prop.Name = "The Database Column Name" then
prop.Value( self ) = "The Database Column Value"
exit
end if
next prop
This is a very generic way for the base class to do all of the hard work, but still assign data into the subclass. Remember, in this particular case -- all of the possible subclasses we ever generate inherit from the same base class. So it's not like we can just push the properties up to the base class so they can be assigned directly.
This is just one of the many uses (or abuses, depending on your school of thought) that you can find for Introspection. There are plenty of other reasons to use it, of course. But if you're pondering why we implemented the feature, hopefully this adds another example to the pile already given. But I still stand by the majority's opinions that you probably won't need introspection in your day to day life -- but when you do need it, it's very hard to live without it.
Oh, and by the way -- I appreciate that you might want to point out all sorts of solutions to the example I gave. You can refrain from doing that though. I'm aware there's always more ways to skin a cat (trust me, I have multiple cats in the house!), and this was just an example. :-)
Aaron, thank you for taking the time to talk about the language in-depth. I've really enjoyed your articles.
Take Care,
SnappyFish
It would seem that introspection would be extremely useful if it was available in RBScript. Any comments? (yes, I know, you can't comment on non-existent features...)
@Paul -- Introspection would be helpful there, but that would also require RBScript to have access to objects aside from the context object. What's more, it would add a whole new level of pain since we'd have to add logic to introspection itself to know whether it was running in sandbox mode or not, since we'd have to filter unsafe methods and properties. Finally, since almost every call in Introspection requires the first parameter to be an object, which would prove problematic is as well. Basically.... I wouldn't hold your breath for that feature showing up soon.
To make things even more interesting, if RB had some kind of system for associating key/value metadata with members - we could call such a hypothetical device an "attribute system" - then the property->database column mapping could be stored in an attribute, allowing properties to have different names than the columns they represent. This could also be a useful place to store information about type mapping, when the underlying DB column types don't exactly match RB datatypes.
@Mars -- what a good idea! I wonder which incredibly brilliant (and dashing) people came up with it? ;-P lol, that's an excellent example of use for a hypothetical attribute system.
Aaron, your database example is almost exactly what I was thinking... I'm about to start a new project soon, and I was considering using introspection for the data model. My idea is a little different than yours:
1) I was going to have a class for each database table record derived from a base class called "Item". The "Item" class would have all the guts for the database interaction and so forth. For example, it would loop through the properties of the object that were prefixed with "db", such as "dbName" and save/get those values from the database (utilizing introspection of course.) I could even create the entire database schema right from the Item object.
2) Then I could create subclasses of the Item class for each database table. For example, my application might have a "Users" class with two properties called "dbName" and "dbPassword" -- which would then represent a table called "Users" in the database. I could then use the class to retrieve and set users from the database, as well as for logic throughout my app.
3) Then it would be super easy to design all the elements of my app, which would simply consist of adding properties to each of my "Item" subclasses through the IDE.
Do you think that would be a good idea? The only thing that concerns me is performance... Would my app take a big hit in performance/memory usage? Using this model will definitely save me time (especially in the initial design phase) but of course it's not worth it if it will slow down my app. Thanks!
@George -- It seems like a reasonable design. But you might want to ask some REAL World 2008 attendees what sort of things they heard about (esp during the keynote) before spending a lot of time coming up with your own system. ;-)
Having the data model classes is useful.
But to make a data model really useful you also want a means to easily get related data.
If you have a Customer instance you might want a way to easily get, without writing SQL, all their Invoices.
There should be a way to do this right from the classes.
Being able to traverse the data model and create complex queries that don't require you to write SQL is really a very useful technique. WebObjects has such a mechanism. I'm sure there are others in other tools.