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.