Archived
This forum has been archived. Please start a new discussion on GitHub.
Equivalent types
kwaclaw
Oshawa, Canada
in Help Center
I am sure this is an old topic:
Working with Slice - even though there are no type aliases allowed - I found that one is still allowed to do this:
sequence<byte> Bytes1;
sequence<byte> Bytes2;
Are Bytes1 and Bytes2 treated as equivalent types?
Or are they treated as separate types?
If the latter, is that not somehow inconsistent, as I cannot do
the same for basic types, like:
string FirstName;
string LastName;
Karl
Working with Slice - even though there are no type aliases allowed - I found that one is still allowed to do this:
sequence<byte> Bytes1;
sequence<byte> Bytes2;
Are Bytes1 and Bytes2 treated as equivalent types?
Or are they treated as separate types?
If the latter, is that not somehow inconsistent, as I cannot do
the same for basic types, like:
string FirstName;
string LastName;
Karl
0
Comments
-
They are separate types from a Slice perspective. However, depending on the language mapping, they might map to the same programming language type. In this example, both would map std::vector<Ice::Byte> in C++.
I don't understand your argument about consistency. "sequence<byte> Bytes1;" defines a type, just like "class Bytes1 { };" would. "string FirstName;" is not a type definition.0 -
marc wrote:They are separate types from a Slice perspective. However, depending on the language mapping, they might map to the same programming language type. In this example, both would map std::vector<Ice::Byte> in C++.
I don't understand your argument about consistency. "sequence<byte> Bytes1;" defines a type, just like "class Bytes1 { };" would. "string FirstName;" is not a type definition.
When I declare these two byte sequencs, Bytes1 and Bytes2, then I have declared to types which are different in name, but equal in nature, so-to-speak.
Why can I not do this for basic types? Why can I not have two types whose nature is string, but they are different types?
This is how it works in Pascal/Delphi, and it is not a type alias. I have found it quite a useful feature.
It would also not violate the Slice principle of disallowing type aliases.
Karl0 -
kwaclaw wrote:When I declare these two byte sequencs, Bytes1 and Bytes2, then I have declared to types which are different in name, but equal in nature, so-to-speak.
Well, you can also do this:struct A { byte value; }; struct B { byte value; };
Both A and B are equivalent, but nevertheless, they are separate Slice types.kwaclaw wrote:Why can I not do this for basic types? Why can I not have two types whose nature is string, but they are different types?
This is simply a design choice we made for Ice. You cannot construct a new type that is the same as an already existing primitive type.kwaclaw wrote:This is how it works in Pascal/Delphi, and it is not a type alias. I have found it quite a useful feature.
It would also not violate the Slice principle of disallowing type aliases.
Karl
Typedefs that define new types instead of just an alias do not translate well to most programming languages. For example, Java doesn't know typedefs at all, and in C++, typedefs do not define a new type. It therefore doesn't make sense to introduce such a concept in Slice, if there are only very few languages that could support it directly.
Our experience about the usefulness of aliases and/or typedefs also differs. We find them pretty much useless. All they did for CORBA IDL was to make specifications much harder to read. The Java language designers apparently share our opinion, since Java doesn't know aliases or typedefs either.0 -
marc wrote:Well, you can also do this:
struct A { byte value; }; struct B { byte value; };
Both A and B are equivalent, but nevertheless, they are separate Slice types.
This is simply a design choice we made for Ice. You cannot construct a new type that is the same as an already existing primitive type.
Typedefs that define new types instead of just an alias do not translate well to most programming languages. For example, Java doesn't know typedefs at all, and in C++, typedefs do not define a new type. It therefore doesn't make sense to introduce such a concept in Slice, if there are only very few languages that could support it directly.
My point was that you actually do have that kind of typedef, just not for primitive types.
Btw, I would not call them typedefs, because they really are type declarations - the terms should not be confused, as the disadvantages of typedefs don't really apply to type declarations that define new types equivalent in nature.marc wrote:Our experience about the usefulness of aliases and/or typedefs also differs. We find them pretty much useless. All they did for CORBA IDL was to make specifications much harder to read. The Java language designers apparently share our opinion, since Java doesn't know aliases or typedefs either.
Well, I don't disagree, but we are really talking about type declarations. The problem that alerted me to this asymmetry in Slice was the following:
I had two types defined as byte sequences:sequence<byte> TokenData; sequence<byte> TokenHash;
And I used them in a few places. Then I thought, maybe I should turn them into strings. So I tried:string TokenData; string TokenHash;
It would make sense to me that this should work. I don't think it has any of the disadvantages of typedefs. In a way, as you already have shown, I could wrap them in a structstruct TokenData { string value; }; struct TokenHash { string value; };
This is basically an equivalent to how the "string declarations" above should work. I think that makes it somewhat clearer that we are not really talking about typedefs. You could consider the former just syntactic sugar for the latter.
Btw, C# does not allow for typedefs and aliases either, and it does not even allow for translating the above byte sequence declarations into separate types (when mapping to arrays), so you already have a mapping problem that you can obviously overcome.
Karl0 -
Hello
What advatange have you in define tow direferent types that represent the same basic structure?
sequence<byte> TokenData;
sequence<byte> TokenHash;
why not simple define one
sequence<byte> ByteSeq;
and use it in both places0 -
kwaclaw wrote:My point was that you actually do have that kind of typedef, just not for primitive types.kwaclaw wrote:Well, I don't disagree, but we are really talking about type declarations. The problem that alerted me to this asymmetry in Slice was the following:
I had two types defined as byte sequences:sequence<byte> TokenData; sequence<byte> TokenHash;
And I used them in a few places. Then I thought, maybe I should turn them into strings. So I tried:string TokenData; string TokenHash;
It would make sense to me that this should work.kwaclaw wrote:I don't think it has any of the disadvantages of typedefs. In a way, as you already have shown, I could wrap them in a structstruct TokenData { string value; }; struct TokenHash { string value; };
This is basically an equivalent to how the "string declarations" above should work. I think that makes it somewhat clearer that we are not really talking about typedefs. You could consider the former just syntactic sugar for the latter.kwaclaw wrote:Btw, C# does not allow for typedefs and aliases either, and it does not even allow for translating the above byte sequence declarations into separate types (when mapping to arrays), so you already have a mapping problem that you can obviously overcome.0 -
xdm wrote:Hello
What advatange have you in define tow direferent types that represent the same basic structure?
sequence<byte> TokenData;
sequence<byte> TokenHash;
why not simple define one
sequence<byte> ByteSeq;
and use it in both places
I don't see any advantages. That's why we usually just include slice/Ice/BuiltinSequences in our Slice files, and then use Ice::ByteSeq.
However, we didn't want to make a special case for sequences of primitives, therefore you can construct them just like any other sequence type.0 -
marc wrote:I know what you mean, you don't want typedefs in a sense of aliases, but define new types that just happen to be the same as existing primitive types. But for the reasons outlined in my last post, we don't want to support this concept in Slice (lack of support in programming languages, and in our opinion also lack of usefulness in general).
Well, I agree with the lack of support in languages, but not with the lack of usefulness, which I believe really is only a problem with aliases. I base this view on own experience with Pascal/Delphi.
Anyway, one can still use the struct wrapper workaround.
Karl0 -
xdm wrote:Hello
What advatange have you in define tow direferent types that represent the same basic structure?
sequence<byte> TokenData;
sequence<byte> TokenHash;
why not simple define one
sequence<byte> ByteSeq;
and use it in both places
The advantage is clarity as to what a specific member represents, and also type safety in general. With the cs:collection directive this would map to two distinct types in C#, for instance, so you could not use one where the other is expected.
Karl0 -
There are two involved in this thread. One is the question of whether Slice should allow typedefs. The answer involves deciding whether a typedef maps to distinct types in a target language or not. In other words, given the following hypothetical Slice definitions
// Hypothetical Slice typedef string Foo; typedef string Bar;
we have to decide whether Foo and Bar are aliases for string, or whether they are distinct types, so I could not pass a Foo where a string is expected, or a Bar where a Foo is expected.
In general, the consensus among many experts is that type aliasing is a bad idea. It offers a readability advantage, because a named type is easier to understand than a primitive type:int cy; int sz; Year cy; // This is more readable Size sz; // Ditto
However, aliased types tend to give a false sense of security. In particular, with C++, there is nothing to stop you from passing a year where a size is expected, or vice-versa, so naming types in this fashion does not offer any additional type safety.
The other issue is that, for middleware, the interface definition language has to be built according to the least common denominator of the target implementation languages. In other words, for every Slice construct, there must be a reasonable way to map the construct into each target language. That, for example, is the reason why Slice enums have the scoping rules they do: they follow the C++ rules, even though many people (including myself) would argue that the C++ scoping rules for enums are silly. But adopting, say, the C# scoping rules for Slice enums would lead to an unnatural mapping for C++. So, the least-common-denominator approach is to use the C++ scoping rules because those also work for C#.
Now, if Slice were to allow aliases for types such as strings, we would have to come up with a reasonable mapping for C# and Java as well as C++. For type aliasing, that's easy: we just throw away the typedef in the generated code so, whereever the Slice definition mentions Foo, the corresponding Java or C# definitions would simply mention string. But that throws away all the supposed type safety, so little is gained by doing this.
On the other hand, we could state that typedefs establish truly distinct types, so Foo and Bar would be unrelated and incompatible types. But doing that would lead to very unnatural language mappings for C++, Java, and C#, and lead to a natural language mapping only for Object Pascal.
Again, that would violate the least-common-denominator principle as well as the principle that each Slice construct must have a reasonable mapping for the majority of implementation languages.
Now look at the current situation: Slice neatly side-steps the issue by simply disallowing typedefs altogether. That way, there are no aliases, each type has exactly one name, and all user-defined types are distinct (even if they are structurally equivalent).
Years ago, I did a lot of work on CORBA IDL and the semantics of the type system. From that experience, I firmly believe that the decision to disallow typedefs in Slice is correct. We had endless problems in CORBA trying to deal with typedefs. In particular, trying to decide which types are equivalent becomes very much more complex once typedefs are permitted, and things can end up in royal mess when a language such as C++, which loses the differences among type aliases, interoperates with a language that preserves them, such as Object Pascal. There are lots of pitfalls and complexities involved that are not immediately apparent, in particular when it comes to dynamic invocation and dispatch. It's best not to go there...
Cheers,
Michi.0 -
michi wrote:Years ago, I did a lot of work on CORBA IDL and the semantics of the type system. From that experience, I firmly believe that the decision to disallow typedefs in Slice is correct. We had endless problems in CORBA trying to deal with typedefs. In particular, trying to decide which types are equivalent becomes very much more complex once typedefs are permitted, and things can end up in royal mess when a language such as C++, which loses the differences among type aliases, interoperates with a language that preserves them, such as Object Pascal. There are lots of pitfalls and complexities involved that are not immediately apparent, in particular when it comes to dynamic invocation and dispatch. It's best not to go there...
I do understand your point about type aliases.
Although I think the two sets of illegal and legal declarations below
are conceptually the same and there is a lack of symmetry,string FirstName; string LastName; struct FirstName { string value; }; struct LastName { string value; };
I see the difficulty with language mappings, as you explained.
Karl0 -
kwaclaw wrote:Although I think the two sets of illegal and legal declarations below are conceptually the same and there is a lack of symmetry,
string FirstName; string LastName; struct FirstName { string value; }; struct LastName { string value; };
I see the difficulty with language mappings, as you explained.
Right. But then, you equally argue that the same symmetry should be provided for user-defined types as follows:struct FirstName { string value; }; struct LastName { string value; }; typedef FirstName ChristianName; typedef LastName FamilyName;
Where do you draw the line?
To me, it is better to use the simple rule that each type must be defined exactly once, and that each defined type is unrelated to every other defined type. For built-in types, such as string, the same is true: they are defined excactly once (namely, predefined by the language), and they are unrelated to every other type.
Cheers,
Michi.0 -
michi wrote:Right. But then, you equally argue that the same symmetry should be provided for user-defined types as follows:
struct FirstName { string value; }; struct LastName { string value; }; typedef FirstName ChristianName; typedef LastName FamilyName;
Where do you draw the line?
If ChristianName and FamilyName are new types, it should be OK,
it should be basically the same asstruct FirstName { string value; }; struct LastName { string value; }; struct ChristianName{ string value; }; struct FamilyName{ string value; };
To me, it is better to use the simple rule that each type must be defined exactly once, and that each defined type is unrelated to every other defined type. For built-in types, such as string, the same is true: they are defined excactly once (namely, predefined by the language), and they are unrelated to every other type.Michi.
Then you should not allowstruct { string value; };
to be defined multiple types. So, where do *you* draw the line. :-)
Apart from Object Pascal this does not seem to be dealt with consistently in most popular languages. Slice allows sequence<type> as a new type, C#, C++, etc. do not allow that for type[]. Apparently a gray area.
Karl0