
You know what? Generics are complicated. Like, really complicated. Like mind-bendingly insanely complicated. Either that or I’m just not all that bright.
It took me a really long time to build enough intelligence into the new low-level reflection library in RubyCLR to handle generics reasonably. I think I’ve rewritten that code 5 times in the past week and a half. Each time I thought I ‘got it’, I’d hit another corner case that would invalidate some fundamental assumption which forced yet another rewrite. Now it’s not a lot of code – maybe 300-400 lines or so all told but it’s really tricky to get right.
Why did I have to rewrite it? In existing builds of RubyCLR in the wild, it’ll handle a core case like:
list = List.of(System::String)
This is probably, what, the 80-90% case of what folks use generics for? So in that sense, people can actually get work done using that implementation. However, to get it to handle all the other cases that you might run into in the wild requires much more work. Also, the existing implementation is rather inefficient in both time and space. There were a lot of hacks I put in there to get things to work, but have to be removed to simplify the codebase and to improve overall performance.
Let’s consider slightly more complex cases:
class G1 {
class N1 { }
class G2 {
class N2 { }
}
}
To get a reference to class N1, you’ll need to do this:
klass = G1.of(System::String)::N1
In C#, you’d do this:
Type klass = typeof(G1<String>.N1);
This case wasn’t all that hard to get right, but it requires that you understand just how this type comes into existence. It makes a lot more sense if we look at it this way:
Type n1 = Type.GetType("G1`1+N1");
Type n2 = n1.MakeGenericType(new Type[] { typeof(String) });
Type n3 = typeof(G1<String>.N1);
Assert.AreEqual(n3, n2);
Assert.IsTrue(n3.IsGenericType);
Type n1 is the generic type definition. You can’t create instances of these things in the wild, you first have to create a closed generic type which is what n2 is.
Now I really wish that I knew this when I first started writing this code, but I’m a user of generics-turned into a language implementor. This kind of stuff isn’t obvious at all since the C# compiler does so much for you. And who really programs this way??? But I digress.
Also, notice that type N1, which is a nested type of a generic type is considered itself to be a generic type.
Now consider the even more complex case of N2:
Type n1 = Type.GetType("G1`1+G2`1+N2");
Type n2 = n1.MakeGenericType(new Type[] { typeof(String), typeof(Int32) });
Type n3 = typeof(G1<String>.G2<Int32>.N2);
Assert.AreEqual(n3, n2);
It’s not all that hard to understand once you saw the first case. So to get a reference to N2 in Ruby, this is what you need to write:
klass = G1.of(System::String)::G2.of(System::Int32)::N2
This is a pretty natural syntax for this, right? The end result is pretty, but it took a lot of blood sweat and tears to get here. This was easily the hardest problem to solve in RubyCLR to date.
This stuff will eventually get checked in, but most of RubyCLR is broken right now on my build (although it resolves types just great!). I’ll check in once I get method invocation working using the new reflection library.
Recent Comments