www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Supertypes, subtypes, and more

reply "bearophile" <bearophileHUGS lycos.com> writes:
In Phobos we have Typedef (that needs some improvements), that 
allows to define a not compatible type.

In D we have "alias this" that inside a struct allows to create a 
new type (with instance object of size equal or larger than the 
original type) with similar semantics of a given type. This 
allows to define a type that acts "like" another.

In Ada there is a standard way to restrict a type. In a Reddit 
thread I have found this simple example of modern Ada code, a 
sufficiently common need:


 My go-to example is social-security numbers, because it's simple
 enough that people can see what's going on as well as provides a
 good way to illustrate how subtypes can allow one to 'ignore'1
 the implementation.

 -- SSN format: ###-##-####
 Subtype Social_Security_Number is String(1..11)
   with Dynamic_Predicate =>
     (for all Index in Social_Security_Number'Range =>
       (case Index is
        when 4|7 => Social_Security_Number(Index) = '-',
        when others => Social_Security_Number(Index) in '0'..'9'
       )
      );

 -- The above declaration can allows me to skip the validity
 -- checks of parameters within the body of a subprogram as
 -- the constraints are checked on subprogram-call.
 -- I do not need to check the validity of the return value,
 -- an attempt to return a non-conformant string will raise
 -- and exception.
 Function Get_SSN( Record : ID ) return Social_Security_Number;

 -- Likewise, passing a non-conformant value to SSN will raise
 -- an exception.
 Procedure Save_SSN( Record : ID; SSN : Social_Security_Number );

I think where you expect a String you can send a Social_Security_Number, but where it expects a Social_Security_Number it doesn't accept a String. (The Dynamic_Predicate and Static_Predicate of modern Ada allow to perform such restrictions.) So I think there are some analogies with OOP. I'd like to do something like this in D, with structs/values. Bye, bearophile
Jun 25 2014
next sibling parent "Meta" <jared771 gmail.com> writes:
On Wednesday, 25 June 2014 at 15:33:16 UTC, bearophile wrote:
 In Phobos we have Typedef (that needs some improvements), that 
 allows to define a not compatible type.

 In D we have "alias this" that inside a struct allows to create 
 a new type (with instance object of size equal or larger than 
 the original type) with similar semantics of a given type. This 
 allows to define a type that acts "like" another.

 In Ada there is a standard way to restrict a type. In a Reddit 
 thread I have found this simple example of modern Ada code, a 
 sufficiently common need:


 My go-to example is social-security numbers, because it's 
 simple
 enough that people can see what's going on as well as provides 
 a
 good way to illustrate how subtypes can allow one to 'ignore'1
 the implementation.

 -- SSN format: ###-##-####
 Subtype Social_Security_Number is String(1..11)
  with Dynamic_Predicate =>
    (for all Index in Social_Security_Number'Range =>
      (case Index is
       when 4|7 => Social_Security_Number(Index) = '-',
       when others => Social_Security_Number(Index) in '0'..'9'
      )
     );

 -- The above declaration can allows me to skip the validity
 -- checks of parameters within the body of a subprogram as
 -- the constraints are checked on subprogram-call.
 -- I do not need to check the validity of the return value,
 -- an attempt to return a non-conformant string will raise
 -- and exception.
 Function Get_SSN( Record : ID ) return Social_Security_Number;

 -- Likewise, passing a non-conformant value to SSN will raise
 -- an exception.
 Procedure Save_SSN( Record : ID; SSN : Social_Security_Number 
 );

I think where you expect a String you can send a Social_Security_Number, but where it expects a Social_Security_Number it doesn't accept a String. (The Dynamic_Predicate and Static_Predicate of modern Ada allow to perform such restrictions.) So I think there are some analogies with OOP. I'd like to do something like this in D, with structs/values. Bye, bearophile

You can do this in C# as well: class SocialSecurityNumber { string _ssn; //Implicitly converts to string public static implicit operator string(SocialSecurityNumber ssn) { return _ssn; } }
Jun 25 2014
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Meta:

 You can do this in C# as well:

 class SocialSecurityNumber
 {

But it's essential to do that on structs. Bye, bearophile
Jun 25 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Wednesday, 25 June 2014 at 19:30:25 UTC, bearophile wrote:
 Meta:

 You can do this in C# as well:

 class SocialSecurityNumber
 {

But it's essential to do that on structs. Bye, bearophile

I believe it works for structs as well.
Jun 25 2014
prev sibling next sibling parent Joseph Rushton Wakeling via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 25/06/14 17:33, bearophile via Digitalmars-d wrote:
 In D we have "alias this" that inside a struct allows to create a new type
(with
 instance object of size equal or larger than the original type) with similar
 semantics of a given type. This allows to define a type that acts "like"
another.

I'd like to mention that this has an unfortunate issue with respect to encapsulation of data. See: http://d.puremagic.com/issues/show_bug.cgi?id=10996 The problem is that if you have a subtype implemented via alias this, struct Foo { SomeOtherStruct payload; alias payload this; // ... other stuff ... } ... then ideally you'd like the payload field to be private, because otherwise you're exposing implementation details, which could be nasty if you ever want to alter the design. (SOMEONE using your code is going to wind up assuming that accessing payload directly is OK.) However, if you try and implement it this way, struct Foo { private SomeOtherStruct payload; public alias payload this; // ... other stuff ... } ... the compiler won't accept it, because the private access to payload clashes with public access via alias this. It's a shame, because the principle -- payload is private, but public access to its fields is available via the alias this -- is a nice one; and it's clearly something that was envisioned for the language (TDPL p. 232, I think). Personally, I'd really appreciate it if the above-mentioned issue could get some compiler dev attention. :-)
Jun 25 2014
prev sibling next sibling parent Justin Whear <justin economicmodeling.com> writes:
On Wed, 25 Jun 2014 15:33:14 +0000, bearophile wrote:
 
 I think where you expect a String you can send a Social_Security_Number,
 but where it expects a Social_Security_Number it doesn't accept a
 String. (The Dynamic_Predicate and Static_Predicate of modern Ada allow
 to perform such restrictions.) So I think there are some analogies with
 OOP.
 
 I'd like to do something like this in D, with structs/values.
 
 Bye,
 bearophile

Does this do everything you're looking for? http://dpaste.dzfl.pl/50531f5b1356 The only potential problem I see is that the invariant presumably won't be compiled in when in release mode.
Jun 25 2014
prev sibling next sibling parent "Tobias Pankrath" <tobias pankrath.net> writes:
     struct Foo
     {
         private SomeOtherStruct payload;
         public alias payload this;

         // ... other stuff ...
     }

 ... the compiler won't accept it, because the private access to 
 payload clashes with public access via alias this.

Did you try using a method? module1 --- struct Subtype { private int x; public int getX() { return x; } alias getX this; } --- module2 --- import module1; void main() { Subtype st = Subtype(2); int x = st; } --- Works for me.
Jun 25 2014
prev sibling next sibling parent "Tofu Ninja" <emmons0 purdue.edu> writes:
On Wednesday, 25 June 2014 at 20:59:00 UTC, Justin Whear wrote:
 On Wed, 25 Jun 2014 15:33:14 +0000, bearophile wrote:
 
 I think where you expect a String you can send a 
 Social_Security_Number,
 but where it expects a Social_Security_Number it doesn't 
 accept a
 String. (The Dynamic_Predicate and Static_Predicate of modern 
 Ada allow
 to perform such restrictions.) So I think there are some 
 analogies with
 OOP.
 
 I'd like to do something like this in D, with structs/values.
 
 Bye,
 bearophile

Does this do everything you're looking for? http://dpaste.dzfl.pl/50531f5b1356 The only potential problem I see is that the invariant presumably won't be compiled in when in release mode.

Now if only we could get that to work with more than one type, such that we could have subtype implicitly convertible to more than one type.
Jun 25 2014
prev sibling next sibling parent Joseph Rushton Wakeling via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 25/06/14 23:10, Tobias Pankrath via Digitalmars-d wrote:
 Did you try using a method?

 module1
 ---

 struct Subtype {
 private int x;
 public int getX() { return x; }
 alias getX this;
 }
 ---

 module2
 ---
 import module1;
 void main()
 {
 Subtype st = Subtype(2);
 int x = st;
 }
 ---

 Works for me.

Yes, someone suggested this to me quite recently in fact. But I think it doesn't solve the real issue -- you're still exposing implementation details, as a user can always call getX directly.
Jun 25 2014
prev sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
(Sorry for the very late answer, I have had to care for animals).

Justin Whear:

 Does this do everything you're looking for?
 http://dpaste.dzfl.pl/50531f5b1356e.

It seems OK. But there is a bit too much boilerplate. So is it possible to create something for Phobos usable like (note the ctor of this SocialSecurityNumber should copy the attributes of the invariant, so if the given invariant is pure, the ctor should be pure. This invariant is pure safe): mixin(Subtype!("SocialSecurityNumber", q{ import std.algorithm: all; import std.ascii: isDigit; assert(impl_.length == 11); assert(impl_[0 .. 3].all!isDigit); assert(impl_[3] == '-'); assert(impl_[4 .. 6].all!isDigit); assert(impl_[6] == '-'); assert(impl_[7 .. 11].all!isDigit); })); Once issue 8864 is implemented (merging this pull: https://github.com/D-Programming-Language/dmd/pull/3680 ) array literals too will be allowed: SocialSecurityNumber[] numbers = ["123-45-6789", "123-45-6785"]; Bye, bearophile
Jul 10 2014