My dog has no type (Expressions with "Superposition" types)


“My dog has no type.”
“How does he smell?”
“Awful!”


This article originally was called “Expressions with no types” but that was a misleading title. This article is really about expressions which have a “quantum superposition” of several types: that’s to say, the expression on its own could be one of several different types, but then the immediate context of that expression makes it collapse down to just one of those types.


Superposition types are used in two of the new features of VB10 — array literals and multi-line lambdas. Really, for just about all programming in VB, users of the language don’t need to care about them: the language just picks the types that are obviously correct. It’s only language lawyers and pedants who will want to understand these special expressions. I remember at my very first undergraduate computer science lecture, the lecturer Frank King handed out the class list and asked for corrections. One student raised his hand, apologized for being pedantic, and said that his name had an “å” with a ring above it, not a plain “a”. Dr King praised him for the correction, saying “computer science needs pedants”. Pedants, please read on…


For completeness, I’ll discuss all the VB expressions that have superposition type: Nothing, Lambdas, Array Literals, AddressOf and CallsToSubs.


To stress again, we’re not talking about “Option Strict Off” and untyped expressions. We’re talking about strongly-typed VB, with Option Strict On, and about which type the compiler picks for these expressions. Answer: it depends on the context they’re in…


Nothing



Dim x0 As Integer = Nothing ‘ can reclassify Nothing as Integer


Dim y0 As Date = Nothing ‘ can reclassify Nothing as Date


 


Dim x1 As Integer = 0


Dim y1 As Date = x1  ‘ error: “cannot convert Integer to Date”


Dim z = Nothing ‘ infers “z As Object”


Let’s start with the biggest example of an expression that has a superposition of types, Nothing. You can assign Nothing to an Integer and to a Date, even though there’s no conversion allowed between Integers and Dates. So “Nothing” is obviously special in some way:


·         In a context where the target type is known (e.g. x0 and y0 above), then the “Nothing” expression has the type of that target, and its value is the default value of that type.


·         In a context where the target type is unknown (e.g. z above), then the “Nothing” expression has type Object and its value is a null pointer.


Lambdas



Dim f0 As Func(Of String) = Function() “hello” ‘ can reclassify lambda as delegate


Dim f1 As Expressions.Expression(Of Func(Of String)) = Function() “hello” ‘ can reclassify lambda as expression


 


Dim f2 = Function() “hello” ‘ infers “f2 As VB$AnonymousDelegate(Of String)”


 


Dim f3 As Func(Of String) = f0


Dim f4 As Func(Of String) = f1 ‘ error: “cannot convert Expression(Of Func(Of String)) to Func(Of String)”


Dim f5 As Func(Of String) = f2


f2 = f5 ‘ error: “cannot convert Func(Of String) to VB$AnonymousDelegate(Of String)”


 


Dim f6 As Expressions.Expression(Of Func(Of String)) = f0 ‘ error: “cannot convert Func(Of String) to Expression(Of Func(Of String))”


Dim f7 As Expressions.Expression(Of Func(Of String)) = f1


Dim f8 As Expressions.Expression(Of Func(Of String)) = f2 ‘ error: “cannot convert Func(Of String) to Expression(Of Func(Of String))”


 


I’ve structured this source code similarly to that for Nothing because they work in similar ways. You can assign a lambda to a Func(Of String) [f0], and to a Expression(Of Func(Of String)) [f1], but you can’t convert a Func(Of String) to an Expression(Of Func(Of String)) [f6] nor vice versa [f4].


·         In a context where the target type is known and a delegate, then a lambda expression has the type of that target, and its value is a newly constructed delegate.


·         In a context where the target type is known and an Expression(Of DelegateType), then a lambda expression as the type of that Expression and its value is an expression-tree corresponding to its body. Note: in VB10 this only works for single-line lambdas: it is an error for multi-line lambdas.


·         In contexts where the target type is unknown, or where it is known but neither a delegate nor an Expression(Of DelegateType), then the lambda expression has the appropriately constructed VB$AnonymousDelegate generic type, and its value is a newly constructed delegate.


This VB$AnonymousDelegate type is interesting because it can be converted to any compatible named delegate type [f5], even though the reverse isn’t true [f2=f5]. Sometimes the conversion from VB$AnonymousDelegate to named delegate can be done just by a CLR cast; sometimes it requires the construction of an intermediate lambda. I can write more about this if anyone asks!


Array Literals



Dim i0 As Integer() = {1, 2, 3}


Dim d0 As Double() = {1, 2, 3}


 


Dim d1 As Double() = i0 ‘ error: “Cannot convert Integer() to Double()”


Dim i1 As Integer() = d0 ‘ error: “Cannot convert Double() to Integer()”


 


Dim i2 = {1, 2, 3} ‘ infers “i2 As Integer()”


 


Again, array literals work in similar ways to Nothing. You can assign an array literal to an Integer() [i0], and you can assign the same array literal to a Double() [d0], even though you can’t convert from Integer() to Double() nor vice versa.


·         In a context where the target type is known and an array, then an array literal has the type of that target, and its value is a newly constructed array. Each of its element expressions are interpreted in a context where their target type is the array element type.


·         In a context where the target type is unknown, or is known but not an array type, then an array literal has type “Array of T with rank r” where T is the dominant type of the element expressions, and the r is the inferred rank of the array literal. The “dominant type of expressions” algorithm is new to VB10. It is also used in multi-line lambdas, and for the IF operator. I’ll write more about it in a later post.


·         Special case: in an initializer context “Dim x(,) = {}” the target type is partially known: it’s rank is known, but its element type is not. In the special case where the target rank is known to be r, where target element type is unknown, and where the array literal is empty, then the array literal has the type “Array of Object with rank r” and its value is an empty array. In all other cases of partially known target types, we interpret the array literal as though its target type were unknown.


The special case looks odd. We needed it because “Dim x(,) = {}” looks like a reasonable statement that should work, and to maintain backwards compatibility with Visual Studio 2008.


Actually, there’s one case where we broke backwards compatibility. “Dim x() = {1,2,3}” would infer “x As Object()” in VS2008, but will infer “x As Integer()” in VS2010. We take backwards compatibility breaks very seriously. In this case we opted for the break to make array literals more intuitive.


AddressOf



Dim a1 As Action(Of Integer) = AddressOf f


a1(Nothing) ‘ prints “integer”


 


Dim a2 As Action(Of String) = AddressOf f


a2(Nothing) ‘ prints “string”


 


Dim a0 = AddressOf f


‘ Error: “AddressOf can’t convert to Object since its not a delegate”


 


 


Sub f(ByVal x As Integer)


    Console.WriteLine(“integer”)


End Sub


 


Sub f(ByVal x As String)


    Console.WriteLine(“string”)


End Sub


 


As seen here, when you write “AddressOf f”, the compiler can’t figure out which “f” you’re referring to without also knowing the target type.


·         In a context where the target type is known and a delegate (e.g. a1 and a2 above), then the “AddressOf f” expression has the type of that target, and its value is a newly constructed delegate.


·         In a context where the target type is unknown, or known and not a delegate, then the “AddressOf f” expression is an error


Calls to Subs



Dim f = Sub() Main()


Dim g = Function() Main() ‘ error: “Expression does not produce a value”


 


I’ve added this section just for completeness. In a single-line lambda, the body of the lambda is an expression. In this case the expression is a call to the sub “Main”. What is the type of this call-to-sub expression? Answer: it doesn’t have a type. That’s why [g] gives the error message it does.

Comments (2)

  1. Speednet says:

    I have a question for you regarding this huge push for "typelessness".

    Why is getting rid of a declared type such an important thing for the Microsoft VB team these days?

    With .NET 2.0, the huge push was generics, which allowed very specific declaration of type at design time, which has the effect of making one’s code crystal clear in intent, and further reduces bugs.

    (Naturally I am referring to code that utlilizes a generic collection, not the generic collection iteself.)

    Taking out all the types may make the compiler very smart, but for maintaing code it can be a nightmare.  Rather than simply reading and understanding the code and intent, one must go through an extra thought process to infer in one’s mind what the compilation of the code will result in.

    I don’t want to go back to the days of Variant, which is where all of this seems to be going, in one shape or another.  I like strongly typed code, for many reasons.

    You may say "all the support is still there, if that’s what you want to do", but that’s not the answer.  By pushing typeless everywhere, the coding community will naturally try to follow your lead, and you know very well that bucking the trend is not healthy for the career of a developer.

  2. ljw1004 says:

    Thanks for the comment, Speednet.

    Here we’re not going back to variant in any way shape or form. All the code here is strongly typed. Almost all the code in this article uses explicit type declarations ("Dim f2 as Func(Of String) = …"). For the few lines that don’t declare types explicitly, they still use type inference to infer (strong) types.

    We’re not taking out types here. The article merely discusses how the compiler figures out the (strong) type of a fragment of an expression.

    Answer: in most cases, the (strong) type of a fragment of an expression is inevitable from that fragment alone. But in just four cases, the (strong) type of a fragment of an expression is determined by the context.

    We needed these four cases because otherwise it would have been awful. Imagine if you had to use not just one flexibly-typed constant "Nothing", but instead a whole family of uniquely typed called "DateNothing" and "IntegerNothing" and so on.

    Question: does weak typing (i.e. Option Strict Off in VB, or the "dynamic" type in C#) have a role? Answer: yes, a limited one, for interop with weakly typed systems like Python and Javascript and HTML DOM, and to some extent with Office COM.

    Question: given that we’re strongly-typed, does type inference have a role? …