IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

Latent Typing Doesn't Mean Implied Types

Thursday 6 January, 2005, 06:34 PM

There's been lots of talk lately concerning the way that C# type checks method calls when the calling function is compiled, even if the caller is a generic function. This imposes more severe restriction than with C++ templates, where type checks are performed at template instantiation time. This means that unlike with C++, C# does not allow you to define this kind of function:

public static void CallDraw<T>(T something)
{
    something.Draw();
}

The C# compiler will complain that it doesn't know anything about this Draw method you're trying to call. (You can write code that has the same effect of course, by using reflection, but that's rather tedious. Or of course you could go for some other design entirely such as passing in a delegate to the function to be called. So it's not as big a problem as everyone seems to be making out. But if you really want to do things this way, then it's annoying.)

Compare that with what C++ does in the exact same scenario. It won't find anything wrong with an equivalent template function because it doesn't check the validity of method calls until the template is instantiated. It will only complain if you attempt to instantiate the template for a type that does not have a suitable Draw method.

The underlying issue here is the point in time at which the type check occurs. (And by 'type check' I mean asking the question: is it valid to perform this operation on the target object type?) There are various different points at which this check can be performed, and different languages have different opinions on which is the right time:

Type Check Time Language and Call Type
Compilation of Calling Function C++: when calling function is not a template (or when the target type is not parameterized)
C#: any normal method call (i.e. excluding reflection-based calls)
Instantiation of Calling Function
(when calling function is template/generic)
C++: when calling function is a template
Method Invocation Python: any call
C#: any reflection-based method call

This shows why C# doesn't support functions like CallDraw - it wants perform the type check at the point at which it compiles the function, long before it knows what types will be used as template parameters.

So why does C# insist on performing this check so early with generic methods? Why doesn't it defer the check until instantiation time like C++ does? The C++ instantiation-time check only works because C++ templates cannot be used outside of the assembly they are defined in. So by the time it has to generate output code for a template function, it already knows about every set of template parameters used for that template. But when it comes to CLR generics, compilers do not know what type parameters might be used. Indeed, that's the very thing that makes CLR generics interesting - the fact that you can bring your own types along as parameters to a generic type defined in component built by someone else.

Given that the problem is caused by doing the type checking too early, solutions are going to take the form deferring this check. So one obvious solution is to resort to invocation-time checks. This is why you can use this style of programming with languages that support dynamic typing, like Python or Visual Basic .NET. And unsurprisingly, you can quite easily write a reflection-based version of the CallDraw method in C#, and it will work perfectly.

So What's Does This Have To Do With Latent Typing Then?

The articles that brought the use of the term "latent typing" into vogue in this particular context were these ones by Bruce Eckel. He pointed out the limitation with an example not unlike CallDraw above, showing C#, C++ and Python versions.

He described this as an example of latent typing.

And he was right. His examples were indeed an example of latent typing. (Something is latent if it is present but not visible. So latent typing has usually been used to describe situations where something has a type, but that type is not explicitly called out in the source code.)

But then he went on to say that there is an implied latent type. So in my example above, Bruce would claim that the type of the the something parameter "is implied by how it is used," as he puts it.

But what would such a type look like? Like this?

public class Foo
{
    public void Draw() { }
}

Or perhaps this?

public class Foo
{
    public int Draw() { return 42; }
}

Or maybe this?

public class Foo
{
    public float Draw() { return 42; }
}

All of the above would be perfectly viable candidates to be plugged into the CallDraw method above. So there is no one implied type. All we really have is a constraint: there must be a public method called Draw that takes no parameters. In general it is not possible to determine the exact signature of a target function from the source code of the call site. (At least it's not possible in C, C++, Java, C#, or Python. In some languages, the call site is precise about the signature of the method being called. For example, method calls in IL always specify the precise target signature, including the return type.)

The fact that there is no implied type becomes even more striking when you consider some more interesting Python examples. Because Python performs its type checks even later than C++, you have even more flexibility:

def speak(speaker, mood):
    if mood == "verbose":
        speaker.WaxLyrical()
    elif mood == "shy":
        speaker.Whisper()
    else:
        speaker.Talk()

Again, let's ask the question: what is the implied type of speaker in this example? And again, there is no answer - there are merely constraints. But this time, the constraints aren't even constant - they will depend on what value is passed in as mood. This may or may not work:

class Brash:
    def WaxLyrical(self):
        print "HELLO!"

If an instance of Brash is passed in as the speaker parameter, then this definition will only work if "verbose" is passed in as the mood parameter. So Brash may or may not be OK. If we pass in "shy" instead, the exact same instance of Brash that worked before will now fail the type check, because speak will attempt to call the non-existent Whisper method.

Note that in C++ we'd see a different result here. Python defers its type checking until the point at which you try to use a member. In a C++ template, the check is done when the template is instantiated. So C++ would actually require all three methods to be present, despite the fact that only one will be used for any given execution of the speak method. So in C++ the constraints a template can impose on its parameter are less dynamic than in Python. However, the constraints are still not precise enough to constitute a type definition, so there is still no implied type - just some constraints.

The constraints that speak imposes on the speaker parameter are now quite involved: if mood is "verbose", speaker must supply a WaxLyrical method, otherwise, if mood is "shy", speaker must provide a Whisper method, and otherwise it must procide a Talk method.

I'm not familiar with any language that allows these kinds of constraints to be expressed in the form of a type definition. (Python, C#, and C++ certainly don't.) So it clearly makes no sense to say that there is an implied type here.

Latent typing doesn't have anything to do with implied types. It's much simpler than that. All latent typing means is that a given variable's type is not defined explcitly, but it is known and has an impact nonetheless. (Exactly what that impact might be varies from one language to another.)

Moreover, latent typing doesn't really have any bearing on whether you can write code like the CallDraw or speak methods shown here. That's just a matter of when type checking is performed.

To illustrate this, consider the following: C# clearly does support latent typing. It must do, otherwise the reflection-based version of CallDraw wouldn't work:

public static void CallDraw<T>(T something)
{
    something.GetType().GetMethod("Draw").Invoke(something, null);
}

All we've done here is opt for a different (and rather cumbersome) style of method invocation. In doing so we have deferred the type check until method invocation time. So we now have the same full flexibility that Python offers us, albeit in a much more clumsy way. This means we can actually go one better than C++ and write functions like the speak method above that decide which members they want to use at runtime.

The fact that we are able to opt for this method-invocation-time type check means that the system must know what the type of something is here. If it didn't, it wouldn't be able to do perform that check. (Or retrieve the Type object for that matter.) So the type is clearly present. And yet it's not evident from the source code - something is declared as type T - one of the type parameters of this generic function. So the type is known but not evident from the source code - it is a latent type. So it's unsurprising that we are able to do the kinds of tricks usually associated with latent typing, even if we had to jump through some reflection hoops to do so.

(And if you believe that resorting to making system calls to exploit the latent type means that C# itself doesn't support latent typing, consider the typeof operator. That is wholly aware of the fact that there's a latent type - take a look at the IL that gets generated for that in a generic method. The language-intrinsic support for the latent type is limited but present in C#, and reflection enables you to exploit the latent type more fully than you can in C++.)

And notice that something is declared here in exactly the same way as in the earlier non-working example. So clearly it had a latent type there too.

So when people complain that C# generics don't support latent typing, they are incorrect. What they are probably really complaining about is that normal function calls in C# are always type checked at compile time, and you have to resort to reflection if you want to exploit the latent type. (Although for virtual functions and interfaces, method binding is based on the latent type. So if you want to exploit the latent type for method selection, that works even without resorting to generics. It's only the type checking of method calls that is resolutely based on the manifest type.)

Why Does C# Check Method Calls at Compile Time?

Earlier I glossed over the details of why C# doesn't support instantiation-time type checks. I said that that basic problem was that the compiler has to generate output code for a generic function, without knowing what type parameters will be used but I didn't fully explain why this makes instantiation-time checking a non-starter.

The essential problem is that IL demands precise information about what is being called. We don't have enough information to generate suitable IL. Once again, consider the generic method we would like to be able to write:

public static void CallDraw<T>(T something)
{
    something.Draw();
}

This does not pin down the requirements all that precisely - latent typing rarely implies exactly what the target type should provide, it merely imposes constraints. And in this case, this code has nothing to say about the return type meaning, that any of the following might be perfectly reasonable bits of IL to generate here:

callvirt instance void   !!0::Draw()
callvirt instance int32  !!0::Draw()
callvirt instance string !!0::Draw()

(There's also the issue that while ILASM is quite happy to compile any of those, the CLR rejects all of them at runtime... It doesn't like it when you use a generic parameter as the target type for a method call, like we're doing with the !!0 types here.)

What we really want is:

callvirt instance * !!0::Draw()

And by that '*' there, I'm meaning to indicate that we don't care what the return type is.

But that would only solve the problem with the return type. What about parameter types? In general, the target function signature does not have to be an exact match for the parameter types being passed in - C# is happy to do certain implicit conversions for us. So if I were to write something.Draw(42); the target function might take an Int32, but it could equally be pretty much any of the other numeric types. Or it could even be a user-defined class that happens to have a custom implicit conversion from int.

The crucial problem here is that if generic functions are going to behave consistently with their non-generic counterparts, all of these implicit conversion rules will need to be applied at the point at which the generic is instantiated. Now for C++ templates that's fine - template instantiations always occur as part of the compilation process, so the C++ compiler can find all of the candidate target functions and apply its normal resolution rules. (Template instantiation may happen some time after the compiler first encounters the source code for the template method in question, but it will always be a part of the same overall compilation process that generates the final executable code.) But for CLR generics, instantiation can occur at runtime, long after compilation. So are we expecting the CLR to perform all of the same implicit conversions for us that the C# compiler would?

Baking in language-specific implicit conversion behaviour into the runtime seems like the wrong thing to do. C# isn't the only language in town, so the CLR shouldn't need knowledge of C#'s requirements. Some languages have quite different rules for binding to target functions.

What it boils down to is that if you want to be able to have cross-language, cross-component generics, there's a price to be paid. The C++ approach only works for intra-component use. (Which is why C++/CLI supports both CLR generics and good old C++ templates.)

Could Microsoft have built a way of capturing constraints better to allow more flexibility? Undoubtedly - they already capture constructor constraint requirements, so the ability to impose method constraints would have been a short step from here. But the fact is that they haven't.

And while this is really a topic for another time, I'm happy with that. I don't actually like this technique of assuming that if the name matches and the parameters are close enough, that it must be the method you wanted. I'm much happier with a functional approach - if you want pluggable functions, just pass the functions in as parameters! Binding on the name and approximate signature strikes me as a recipe for disaster, and just feels like a kludge to work around the lack of support for higher order functions. Fortunately, .NET gives us delegates, so we don't need these kinds of kludges.

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk