Monday, December 7, 2009
I've been following Guido's suggestion about keeping the methods that relate to reflection in a separate namespace from the actual class methods. For each user-defined class, there is a separate instance of tart.reflect.Type which contains information about the type name, base classes, lists of methods, and so on.

This reflection structure is entirely separate from the regular "class" object which contains the jump tables and superclass array. in fact, the class contains no references to the reflection data at all, as the relationship is one-way only. This means that if you aren't using reflection, the various data structures can be deleted by the linker during optimization. Unfortunately, this also means that if you do need reflection, you have to explicitly register the classes that you want to reflect. Since you can register whole modules and/or packages with a single call, this is not that much of a hardship.

So now that we have these two data structures, how do you get from one to the other? Here's what I implemented today:

Normally, when a type name is used, it is either used as a constructor: Foo(), or as a namespace for statics: Foo.method. However, what happens if we attempt to use Foo as a value? Another way to ask this question is to say "what is the type of the word 'Foo' in the source code?" The answer is that Foo is a type literal. Specifically, it's type is TypeLiteral[Foo], meaning that it's an type that has a template parameter which is the type represented by the literal.

There are a couple of different ways you can use a type literal. First, you can convert it into a pointer to the type reflection object using Type.of(). So for example, if I say Type.of(String), what I get is the reflection info for class String. (The 'of' method in class Type is an intrinsic that does this.) Note that this use of 'of' automatically pulls in the reflection data for that type, so there's no need to explicitly register that class.

The other way you can use a type literal is by binding it's template argument to a type variable. For example:

   def newInstance[%T](type:TypeLiteral[T]) -> T { return T(); }

If we pass "String" as the argument, what actually gets passed is TypeLiteral[String]. This in turn causes the type variable T to be bound to 'String', at which point the definition of 'T' is now available in the body of the function. Note that we never actually use the value of the type literal in this case, only it's type - which will most often be the case, since TypeLiterals have no properties other than the single type parameter.

Why did we not simply declare the argument as T rather than TypeLiteral[T]? Because if we're passing in, say, a String, then 'T' means an actual instance of a String, whereas TypeLiteral[T] means we are talking about the String *type*.


Post a Comment