Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Question regarding implicit conversions in the C# language specification

Section 6.1 Implicit conversions defines an identity conversion thusly:

An identity conversion converts from any type to the same type. This conversion exists such that an entity that already has a required type can be said to be convertible to that type.

Now, what is the purpose of sentences such as these?

(In §6.1.6 Implicit reference conversions)

The implicit reference conversions are:

  • [...]
  • From any reference-type to a reference-type T if it has an implicit identity or reference conversion to a reference-type T0 and T0 has an identity conversion to T.

and:

(In §6.1.7 Boxing conversions)

  • A value type has a boxing conversion to an interface type I if it has a boxing conversion to an interface type I0 and I0 has an identity conversion to I.

Initially they seem redundant (tautologous). But they must be there for a purpose, so why are they there?

Can you give an example of two types T1, T2 such that T1 would not be implicitly convertible to T2 if it weren’t for the above-quoted paragraphs?

like image 303
Timwi Avatar asked Sep 17 '10 15:09

Timwi


2 Answers

Update on 22-Sep-2010:

I doubt anybody is going to read this besides Timwi. Even so, I wanted to make a few edits to this answer in light of the fact that a new answer has now been accepted and the debate still continues (at least in my perhaps imaginary world) on whether or not the quoted excerpts of the spec are technically redundant. I am not adding much, but it's too substantial to fit in a comment. The bulk of the update can be found under the heading "Conversion involving the dynamic type" below.


Update on 19-Sep-2010:

In your comment:

[T]his doesn’t make sense.

Damn, Timwi, you say that a lot. But all right, then; you've put me on the defensive, so here goes!

Disclaimer: I have definitely not examined the spec as closely as you have. Based on some of your recent questions it seems like you've been looking into it quite a bit lately. This is naturally going to make you more familiar with a lot of details than most users on SO; so this, like most answers you're likely to receive from anyone other than Eric Lippert, may not satisfy you.

Different premises

Firstly, the premise of your question is that if the statements highlighted are redundant, then they serve no purpose. My answer's premise is that redundant statements are not necessarily without purpose if they clarify something that isn't obvious to everyone. These are contradictory premises. And if we can't agree on premises, we can't have a straightforward logical argument. I was simply asking you to rethink your premise.

Your response, however, was to reiterate your premise: "If the sentences are truly redundant, then they only confuse the reader and don't clarify anything."

(I like how you set yourself up as the representative for all readers of the spec there, by the way.)

I can't blame you for holding this position, exactly. I mean, it does seem obvious. And I didn't give any concrete examples in my original answer. So below I will try to include some concrete examples. But first, let me take a step back and offer my take on why this weird identity conversion concept exists in the spec in the first place.

The purpose of the identity conversion definition

Upon first glance, this definition seems rather superfluous; isn't it just saying that an instance of any type T is convertible to ... well, to T? Yes, it is. But I hypothesize* that the purpose of this definition is to provide the spec with the proper vocabulary to utilize the concept of type identity in the context of discussing conversions.

This allows for statements about conversions which are essentially transitive in nature. The first point you quoted from the spec as an example of a tautological statement falls into this category. It says that if an implicit conversion is defined for some type (I'll call it K) to another type T0 and T0has an identity conversion to T, then K is implicitly convertible to T. By the definition of identity conversion given above, "has an identity conversion to" really means "is the same type as." So the statement is redundant.

But again: the identity conversion definition exists in the first place to equip the spec with a formal language for describing conversions without having to say things like "if T0 and T are really the same type."

OK, time for concrete examples.

Where the existence of an implicit conversion might not be obvious to some developers

Note: A much better example has been provided by Eric Lippert in his answer to the question. I leave these first two examples as only minor reinforcements of my point. I have also added a third example that concretizes the identity conversion that exists between object and dynamic as pointed out in Eric's answer.

Transitive reference conversion

Let's say you have two types, M and N, and you've got an implicit conversion defined like this:

public static implicit operator M(N n);

Then you can write code like this:

N n = new N();
M m = n;

Now let's say you've got a file with this using statement up top:

using K = M;

And then you have, later in the file:

N n = new N();
K k = n;

OK, before I proceed, I realize that this is obvious to you and me. But my answer is, and has been from the beginning, that it might not be obvious to everyone, and therefore specifying it--while redundant--still has a purpose.

That purpose is: to make clear to anyone scratching his or her head, looking at that code, it is legal. An implicit conversion exists from N to M, and an identity conversion exists from M to K (i.e., M and K are the same type); so an implicit conversion exists from N to K. It isn't just logical (though it may be logical); it's right there in the spec. Otherwise one might mistakenly believe that something like the following would be necessary:

K k = (M)n;

Clearly, it isn't.

Transitive boxing conversion

Or take the type int. An int can be boxed as an IComparable<int>, right? So this is legal:

int i = 10;
IComparable<int> x = i;

Now consider this:

int i = 10;
IComparable<System.Int32> x = i;

Again, yes, it may be obvious to you, me, and 90% of all developers who might ever come across it. But for that slim minority who don't see it right away: a boxing conversion exists from int to IComparable<int>, and an identity conversion exists from IComparable<int> to IComparable<System.Int32> (i.e., IComparable<int> and IComparable<System.Int32> are the same type); so a boxing conversion exists from int to IComparable<System.Int32>.

Conversion involving the dynamic type

I'm going to borrow from my reference conversion example above and just tweak it slightly to illustrate the identity relation between object and dynamic in version 4.0 of the spec.

Let's say we have the types M<T> and N, and have defined somewhere the following implicit conversion:

public static implicit operator M<object>(N n);

Then the following is legal:

N n = new N();
M<dynamic> m = n;

Clearly, the above is far less obvious than the two previous examples. But here's the million-dollar question: would the above still be legal even if the excerpts from the spec quoted in the question did not exist? (I'm going to call these excerpts Q for brevity.) If the answer is yes, then Q is in fact redundant. If no, then it is not.

I believe the answer is yes.

Consider the definition of identity conversion, defined in section 6.1.1 (I am including the entire section here as it is quite short):

An identity conversion converts from any type to the same type. This conversion exists such that an entity that already has a required type can be said to be convertible to that type.

Because object and dynamic are considered equivalent there is an identity conversion between object and dynamic, and between constructed types that are the same when replacing all occurences of dynamic with object. [emphasis mine]

(This last part is also included in section 4.7, which defines the dynamic type.)

Now let's look at the code again. In particular I'm interested in this one line:

M<dynamic> m = n;

The legality of this statement (disregarding Q -- remember, the issue being discussed is the hypothetical legality of the above statement if Q did not exist), since M<T> and N are custom types, depends on the existence of a user-defined implicit conversion between N and M<dynamic>.

There exists an implicit conversion from N to M<object>. By the section of the spec quoted above, there is an identity conversion between M<object> and M<dynamic>. By the definition of identity conversion, M<object> and M<dynamic> are the same type.

So, just as in the first two (more obvious) examples, I believe it is true that an implicit conversion exists from N to M<dynamic> even without taking Q into account, just as it is true that an implicit conversion exists from N to K in the first example and that a boxing conversion exists from int to IComparable<System.Int32> in the second example.

Without Q, this is much less obvious (hence Q's existence); but that does not make it false (i.e., Q is not necessary for this behavior to be defined). It just makes it less obvious.

Conclusion

I said in my original answer that this is the "obvious" explanation, because it seemed to me you were barking up the wrong tree. You initially posed this challenge:

Can you give an example of two types T1, T2 such that T1 would not be implicitly convertible to T2 if it weren’t for the above-quoted paragraphs?

No one's going to meet this challenge, Timwi, because it's impossible. Take the first excerpt about reference conversions. It is saying that a type K is implicitly convertible to a type T if it is implicitly convertible to T0 and T0 is the same as T. Deconstruct this, put it back together, and you're left with an obvious tautology: K is implicitly convertible to T if it's implicitly convertible to T. Does this introduce any new implicit conversions? Of course not.

So maybe Ben Voigt's comment was correct; maybe these points that you're asking about would've been better placed in footnotes, rather than in the body of the text. In any case, it's clear to me that they are redundant, and so to start with the premise they cannot be redundant, or else they wouldn't be there is to embark on a fool's errand. Be willing to accept that a redundant statement may still shed some light on a concept that may not be obvious to everyone, and it will become easier to accept these statements for what they are.

Redundant? Yes. Tautologous? Yes. Pointless? In my opinion, no.

*Obviously, I did not have any part in writing the C# language specification. If I did, this answer would be a lot more authoritative. As it is, it simply represents one guy's feeble attempt to make sense of a rather complex document.


Original answer

I think you're (perhaps intentionally) overlooking the most obvious answer here.

Consider these two sentences in your question:

(1) Initially they seem redundant (tautologous). (2) But they must be there for a purpose, so why are they there?

To me, the implication of these two sentences together is that a tautologous statement serves no purpose. But just because a statement follows logically from established premises, that does not make it obvious to everyone. In other words even if (1) is true, the answer to (2) may simply be: to make the described behavior clear to anyone reading the spec.

Now you might argue that even if something is not obvious, it still does not belong in a specification if it is providing a redundant definition. To this potential objection, I can only say: be realistic. It's not really practical (in my opinion) to comb through a document stripping out all statements which are simply stating facts that could have been deduced from prior statements.

If this were a common practice, I think you'd find a lot of literature out there -- not just specs, but research papers, articles, textbooks, etc. -- would be a lot shorter, denser, and more difficult to understand.

So: yes, perhaps they are redundant. But that does not negate their purpose.

like image 171
Dan Tao Avatar answered Sep 30 '22 11:09

Dan Tao


Section 4.7 of the specification notes that there is an identity conversion from Foo<dynamic> to Foo<object> and vice versa. The portion of the spec you quoted is written to ensure that this case is handled. That is, if there is an implicit reference conversion from T to C<object, object> then there is also an implicit reference conversion to C<object, dynamic>, C<dynamic, object> and C<dynamic, dynamic>.

One might reasonably point out that (1) the intention of these phrases is unobvious - hence your question - and confusing, and (2) that the section on identity conversions ought to cross-reference the section on dynamic conversions, and (3) phrases like this in the spec make it difficult for an implementor of the specification to clearly translate the spec language into an implementation. How is one to know if any such type exists? The spec need not specify exact algorithms, but it would be nice if it gave more guidance.

The spec is, sadly, not a perfect document.

like image 34
Eric Lippert Avatar answered Sep 30 '22 13:09

Eric Lippert