Archived

This forum has been archived. Please start a new discussion on GitHub.

Equivalent types

kwaclaw
kwaclaw Oshawa, Canada
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

Comments

  • marc
    marc Florida
    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.
  • kwaclaw
    kwaclaw Oshawa, Canada
    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.

    Karl
  • marc
    marc Florida
    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.
  • kwaclaw
    kwaclaw Oshawa, Canada
    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 struct
    struct 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.

    Karl
  • xdm
    xdm La Coruña, Spain
    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
  • marc
    marc Florida
    kwaclaw wrote:
    My point was that you actually do have that kind of typedef, just not for primitive types.
    I don't think so. There are constructed types and there are primitive types. Sequences are constructed types (just like structs, classes, or dictionaries), not some form of typedef.
    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.
    The first example is the definition (not declaration) of constructed types. The second example would be the definition of primitive types. You can't get around the definition of constructed types, but you don't have to define primitive types that are equivalent to already existing primitive types.
    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 struct
    struct 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.
    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).
    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.
    Yes, and the same is true for Java. There is no problem to map these types, but the additional type information gets lost in the mapping, meaning that the developer would never see the Slice-defined primitive types.
  • marc
    marc Florida
    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.
  • kwaclaw
    kwaclaw Oshawa, Canada
    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.

    Karl
  • kwaclaw
    kwaclaw Oshawa, Canada
    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.

    Karl
  • 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.
  • kwaclaw
    kwaclaw Oshawa, Canada
    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.

    Karl
  • 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.
  • kwaclaw
    kwaclaw Oshawa, Canada
    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 as
    struct 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 allow
    struct { 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.

    Karl