www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Walter's second axiom

reply "Janice Caron" <caron800 googlemail.com> writes:
As discussed elsewhere, Walter's second axiom states: for any type T,
it should be possible to construct a struct S which behaves exactly
like a T, by wrapping T inside a struct and adding extra member
functions, like so:

    struct S
    {
        T t;
        /* functions */
    }

However, there is at least one violation of the axiom: inheritance. As follows:

    class C {}
    struct S { C c; }

I can do

    class D : C {}

but I can't do

    class D : S {}

thus, S cannot be made to behave exactly like C, and so the axiom does not hold.

The solution is to provide structs with some mechanism that allows
classes to inherit from them - such as, for example, opInherit()

    struct S
    {
        C c;
        C opInherit() { return c; }
    }

A class could then inherit from either a class, or from a struct which
supplies opInherit(). opInherit() itself must, of course, return
either a class, or a struct which supplies opInherit().

This is not a feature request as such. I'm just mentioning - from a
purely theoretical standpoint - a violation of Walter's second axiom,
and a possible remedy.

(And just to stir things up further, I think that interfaces violate
the second axiom also) :-)
Dec 08 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Janice Caron wrote:
 As discussed elsewhere, Walter's second axiom states: for any type T,
 it should be possible to construct a struct S which behaves exactly
 like a T, by wrapping T inside a struct and adding extra member
 functions, like so:
 
     struct S
     {
         T t;
         /* functions */
     }
 
 However, there is at least one violation of the axiom: inheritance. As follows:
 
     class C {}
     struct S { C c; }
 
 I can do
 
     class D : C {}
 
 but I can't do
 
     class D : S {}
 
 thus, S cannot be made to behave exactly like C, and so the axiom does not
hold.

There are various other ones that have to do with return of lvalues (or lack thereof). For instance, if T is int[], you can do t[0]++ but you can't do that for a struct that wraps an int[]. Also the thing about default initializers for struct wrappers. Perhaps that's just a bug, though. Walter hasn't commented whether he intends to make that work or not. The example is wrap a numeric type and give it all the bells it needs to initialize it with a regular number, like MyInt x = 5. Now try to use it as a paramter with default: void func(MyInt x = 5) // error But I think axiom 2 is great and I'd love to see a solution for all of the holes that prevent making a wrapper behave exactly like another type.
 The solution is to provide structs with some mechanism that allows
 classes to inherit from them - such as, for example, opInherit()
 
     struct S
     {
         C c;
         C opInherit() { return c; }
     }
 
 A class could then inherit from either a class, or from a struct which
 supplies opInherit(). opInherit() itself must, of course, return
 either a class, or a struct which supplies opInherit().
 
 This is not a feature request as such. I'm just mentioning - from a
 purely theoretical standpoint - a violation of Walter's second axiom,
 and a possible remedy.
 
 (And just to stir things up further, I think that interfaces violate
 the second axiom also) :-)

I think maybe that's going to far. There are some places where you cannot and should not have a wrapper act like the type it wraps. The main one that comes to mind is an 'is' check. is(MyWrapper == RealClass). Obviously that should fail. But it stands to reason that there are perhaps some other operations that are so closely tied to the actual type that they shouldn't be expected to work with a wrapper. I think inheritance is maybe one of those. --bb
Dec 08 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 This is not a feature request as such. I'm just mentioning - from a
 purely theoretical standpoint - a violation of Walter's second axiom,
 and a possible remedy.

That axiom didn't really exist until Andrei started pointing out to me all the advantages of being able to wrap types in a struct. So we can expect some historical problems with it. But it is a very useful one going forward with changes to the language, as it keeps us from making things worse. I suspect that the closer we can adhere to that axiom, the better generic programming will work. Other axioms in D have been there from the beginning, and have helped keep the language on track. For example: 1) D can be lexed with no syntactic information 2) D can be parsed with no semantic information 3) Modules can be imported in any order without affecting their meaning 4) The meaning of imported declarations is not affected by the context of where they are imported
Dec 08 2007
next sibling parent Jeff Nowakowski <jeff dilacero.org> writes:
Janice Caron wrote:
 
 Oh dear - now you've gone and renumbered them! How can we refer to them now?
:-)

enumerate them with descriptive names? -Jeff
Dec 08 2007
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 
 Other axioms in D have been there from the beginning, and have helped 
 keep the language on track. For example:
 
 1) D can be lexed with no syntactic information
 2) D can be parsed with no semantic information
 3) Modules can be imported in any order without affecting their meaning
 4) The meaning of imported declarations is not affected by the context 
 of where they are imported

I guess mixins are an obvious exception to 4? Sean
Dec 09 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 Walter Bright wrote:
 Other axioms in D have been there from the beginning, and have helped 
 keep the language on track. For example:

 1) D can be lexed with no syntactic information
 2) D can be parsed with no semantic information
 3) Modules can be imported in any order without affecting their meaning
 4) The meaning of imported declarations is not affected by the context 
 of where they are imported

I guess mixins are an obvious exception to 4?

Not really, because mixins are *defined* to have meaning only in the instantiation context. How/where you do the import doesn't affect this.
Dec 09 2007
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

Can you please share with us all the advantages of "Any type T can be wrapped inside a struct S, and that S can be made to behave as a typedef for type T." Under what conditions can it behave exactly like a typedef? If it behaves *exactly* as a typedef under all circumstances, then it can't have any additional data members or member functions and must behave exactly like the original. If that's true, why wrap it in a struct at all? I assume there has to be some way to bend that. Candidates in my mind are extra data members and extra member functions. Extra data members seems less likely because a cast from S to T and back to S would lose data (something that typedef's don't have happen). Of course, this may fit into the grand scheme of opImplicitCast. I also wonder if extending a class may be an alternative to wrapping it in a struct. Maybe wrapping in a struct could be restricted to value types? I just don't understand this enough yet to give any credible suggestions.
Dec 11 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Jason House wrote:
 Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

Can you please share with us all the advantages of "Any type T can be wrapped inside a struct S, and that S can be made to behave as a typedef for type T." Under what conditions can it behave exactly like a typedef? If it behaves *exactly* as a typedef under all circumstances, then it can't have any additional data members or member functions and must behave exactly like the original. If that's true, why wrap it in a struct at all? I assume there has to be some way to bend that. Candidates in my mind are extra data members and extra member functions. Extra data members seems less likely because a cast from S to T and back to S would lose data (something that typedef's don't have happen). Of course, this may fit into the grand scheme of opImplicitCast. I also wonder if extending a class may be an alternative to wrapping it in a struct. Maybe wrapping in a struct could be restricted to value types? I just don't understand this enough yet to give any credible suggestions.

I don't think the idea is to restrict it to the original set of methods. Adding or removing methods is ok. For instance if you want to make something that's like an array but can't have its length changed then then just leave out the .length setter property, etc. Even if you keep the exact same interface as the original type it's still useful to be able to intercept methods to provide notifications. For instance in a GUI program you could create a float wrapper that acts just like a regular float, except whenever you set or change its value it automatically sends out notifications to anyone who's interested in the value. Or maybe you're writing some distributed processing thing and setting the value is going to cause packets to be sent out to another process over the net that is mirroring this one's state. --bb
Dec 11 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Jason House wrote:
 Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

Can you please share with us all the advantages of "Any type T can be wrapped inside a struct S, and that S can be made to behave as a typedef for type T."

You might, for example, want to create an associative array that behaved slightly differently, but use it just like an AA.
Dec 11 2007
parent reply Jason House <jason.james.house gmail.com> writes:
Walter Bright wrote:

 Jason House wrote:
 Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

Can you please share with us all the advantages of "Any type T can be wrapped inside a struct S, and that S can be made to behave as a typedef for type T."

You might, for example, want to create an associative array that behaved slightly differently, but use it just like an AA.

... so even without the const issues, this type of functionality does not yet work in D? As far as I know, AA's are the only thing that support the in operator.
Dec 11 2007
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Jason House wrote:
 Walter Bright wrote:
 
 Jason House wrote:
 Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

wrapped inside a struct S, and that S can be made to behave as a typedef for type T."

slightly differently, but use it just like an AA.

.... so even without the const issues, this type of functionality does not yet work in D? As far as I know, AA's are the only thing that support the in operator.

opIn_r Overload and be happy. -- Daniel
Dec 11 2007
prev sibling parent reply Derek Parnell <derek nomail.afraid.org> writes:
On Tue, 11 Dec 2007 18:57:17 -0500, Jason House wrote:

 Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

Can you please share with us all the advantages of "Any type T can be wrapped inside a struct S, and that S can be made to behave as a typedef for type T." Under what conditions can it behave exactly like a typedef? If it behaves *exactly* as a typedef under all circumstances, then it can't have any additional data members or member functions and must behave exactly like the original. If that's true, why wrap it in a struct at all?

I don't think that Walter was suggesting that one actually does this ... struct LONG { long x; } LONG foo; because that would be a bit pointless, and because it would also show that you need to use different syntax when using structs rather than typedefs ... struct longS { long x; } typedef long longT ; void main() { longS foo; longT bar; foo.x = 1; // One can't yet do "foo = cast(longS)1;" bar = cast(longT)2; }
 I assume there has to be some way to bend that.  Candidates in my mind are
 extra data members and extra member functions.  Extra data members seems
 less likely because a cast from S to T and back to S would lose data
 (something that typedef's don't have happen).  Of course, this may fit into
 the grand scheme of opImplicitCast.
 
 I also wonder if extending a class may be an alternative to wrapping it in a
 struct.  Maybe wrapping in a struct could be restricted to value types?  I
 just don't understand this enough yet to give any credible suggestions.

I needed a Currency data type. I chose to use a struct to wrap a long plus some methods to get it. So my Currency is really a smart long but it behaves 'like' a value type except for the special syntax required because it *is* a struct. Of course I'd like to see a truly smart value type functionality added to D, in which one could use the same syntax used for built-in value types. Currency actual_balance, available_balance, credit_limit, overlimit_amount ; Date overlimit_date, current_processing_date ; int overlimit_days; . . . if ( actual_balance <= credit_limit ) overlimit_amount = 0; else overlimit_amount = credit_limit - actual_balance; if (available_balance <= credit_limit) { overlimit_date = null; overlimit_days = 0; } else overlimit_days = current_processing_date - overlimit_date; -- Derek (skype: derek.j.parnell) Melbourne, Australia 12/12/2007 12:59:14 PM
Dec 11 2007
parent Jason House <jason.james.house gmail.com> writes:
Derek Parnell Wrote:

 On Tue, 11 Dec 2007 18:57:17 -0500, Jason House wrote:
 
 Walter Bright wrote:
 That axiom didn't really exist until Andrei started pointing out to me
 all the advantages of being able to wrap types in a struct. So we can
 expect some historical problems with it.

Can you please share with us all the advantages of "Any type T can be wrapped inside a struct S, and that S can be made to behave as a typedef for type T." Under what conditions can it behave exactly like a typedef? If it behaves *exactly* as a typedef under all circumstances, then it can't have any additional data members or member functions and must behave exactly like the original. If that's true, why wrap it in a struct at all?

I don't think that Walter was suggesting that one actually does this ... struct LONG { long x; } LONG foo; because that would be a bit pointless, and because it would also show that you need to use different syntax when using structs rather than typedefs ...

That still leaves me wondering what he's suggesting that we do, and how it fits into expanding generic programming support. It seems like this mailing list has a lot of trouble convincing Walter of our viewpoints. Somehow, Andrei has succeeded and I have to assume that there were a number of good arguments and motivating examples. I'd also expect axioms to be well worded and iron-clad concepts. They are after-all, an acid test for addition of features. If the goal is really an ability to define a new type that can be used in a similar way to another type (including application of const), I think that's a lot different than wrapping in a struct and a lot different than defining a typedef. As I understand the const/tail-const problems, I think that char[], const(char) [], and const char[] are three distinct types. Using a reference struct of struct foo{ char[] x}, the middle array type (tail const) is impossible to achieve with a simple application of const to the type... foo and const foo are well defined, but tail const makes little sense. I'd like to provide better feedback in the const debate, but I need to really understand what the real goal is. For example, arrays and associative arrays tend to pose a unique problem with axiom #2. I've been trying to think of arrays and associative arrays as nothing special... array!(T) and AA!(T) instead of specialized syntax. Looking at normal arrays, the three const types would translate to array!(char), array!(const(char)), const array!(char). In that form, tail const really has a different feel... If we have templated structs in a similar way, it may be a good way to think about this issue... I just don't know yet.
Dec 12 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 12/8/07, Walter Bright <newshound1 digitalmars.com> wrote:
 1) D can be lexed with no syntactic information
 2) D can be parsed with no semantic information
 3) Modules can be imported in any order without affecting their meaning
 4) The meaning of imported declarations is not affected by the context
 of where they are imported

Oh dear - now you've gone and renumbered them! How can we refer to them now? :-) It occurs to me that the second axiom (...that's using the old numbering...) could in time render the keyword "typedef" irrelevant. That is, struct S : T {} could replace typedef T S; Of course, I'm being extremely optimistic here in assuming the struct syntax above will ever be part of D! :-)
Dec 08 2007
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On Dec 11, 2007 11:57 PM, Jason House <jason.james.house gmail.com> wrote:
 Can you please share with us all the advantages of "Any type T can be
 wrapped inside a struct S, and that S can be made to behave as a typedef
 for type T."


 Under what conditions can it behave exactly like a typedef?  If it behaves
 *exactly* as a typedef under all circumstances, then it can't have any
 additional data members or member functions and must behave exactly like
 the original.  If that's true, why wrap it in a struct at all?

It's the difference between CAN and MUST. The idea that it is /possible/ to make a type which emulates another type. But no one ever said it was compulsory.
Dec 11 2007