Please consider struct Guid<T>

Topics: C# Language Design, General, VB Language Design
Jul 11, 2014 at 1:35 AM
Edited Jul 11, 2014 at 1:51 AM
public struct Guid<T> : GuidGeneric, IFormattable, IComparable, IComparable<Guid<T>>, IEquatable<Guid<T>>, IComparable<Guid>, IEquatable<Guid> { … }

public struct GuidTypes {
   public enum Customer{ }
   public enum Followup { }
}

internal class CustomerAO : AuditableAO, Customer {
    public CustomerAO() {
         Id = Guid<GuidTypes.Customer>.Default;
    }

    public Guid<GuidTypes.Customer> Id { get; set; }
}
Jul 11, 2014 at 6:59 AM
Why you dont write it yourself?
Jul 16, 2014 at 5:07 AM
Olmo wrote:
Why you dont write it yourself?
I have implemented this in a large scale project.. The code spinets are from it. If you notice 'GuidTypes' in the Struct decleration above and its use in the 'Guid<GuidTypes.Customer>'. This works but I am fairly certain I am programming to a side effect in the compiler and Ander's would puke if he saw what I did, and got away with.

Strongly typed Guids like 'Guid<GuidTypes.Customer>' and Primary Ids in Tables as Guids are highly cohesive unless someone miss assigned a Guid ID from one object to another type of objects Id and then persists it to a database; Ouch. Strongly Typed Guids would Make management of DomainObjects Ids' type safe. in addition there are PickLists and Individual List Items assigned to DomainObjects properties that could benefit from this.

My compiler is my first most valuable unit test.
Jul 16, 2014 at 7:59 AM
We also have some similar construct. It's called Lite<T> but has some important differences:
  • it's based on int, not guid: I would like to use guids.... If they will just be shorter :)
  • it's generic on the entity type: in Signum Framework we can transfer the entities to client applications, and we share the same model, so that make sense for us. We use WCF NetDataContract to preserve types. With a more distribute architecture where a customer is a different DTO in each application your solution makes sense.
  • Lite<T> also carries the entity ToString, that is extremely useful so we can return lites in comboboxes, autocompletes, reports, etc...
  • lites are also covariant, so you can assign a Lite<Cat> in a Lite<Animal> variable.
So, why the essence is similar, the devil is in the details. I don't see a lot of value integrating this in the BCL, much less in the language!
Marked as answer by TomasMatousek on 7/16/2014 at 10:47 PM
Jul 16, 2014 at 6:38 PM
Olmo wrote:
We also have some similar construct. It's called Lite<T> but has some important differences:
  • it's based on int, not guid: I would like to use guids.... If they will just be shorter :)
  • it's generic on the entity type: in Signum Framework we can transfer the entities to client applications, and we share the same model, so that make sense for us. We use WCF NetDataContract to preserve types. With a more distribute architecture where a customer is a different DTO in each application your solution makes sense.
  • Lite<T> also carries the entity ToString, that is extremely useful so we can return lites in comboboxes, autocompletes, reports, etc...
  • lites are also covariant, so you can assign a Lite<Cat> in a Lite<Animal> variable.
So, why the essence is similar, the devil is in the details. I don't see a lot of value integrating this in the BCL, much less in the language!
Could you post the code and its use as referenced to a Guid<T>?
Jul 17, 2014 at 8:46 AM
Jul 19, 2014 at 3:31 AM
Edited Jul 19, 2014 at 3:36 AM
I don't think we are talking about the same idea. Your wrapping a class that will have an Integer ID as part of the wrapper and assigned in the constructor. I am not familiar with the use of the 'Lite<out T>' out I am asuming reserved word in a generic; something to become familiar with. I am not trying to wrap the T in Guid<T>. The T Guid<T> is only used to separate one typed Guid from another as far as the compiler is concerned; please reference the explanation and code at the beginning of the thread. "Guid<GuidTypes.Customer>" and "Guid<GuidTypes.Followup>" are declared as "enum Customer{ }" and "enum Followup { }"; both empty emurations. Since they are both empty enumerations they do nothing except fix distinct typing for the compiler and as such will not permit "customer.Id = followup.Id;"

"Guid<T>" is an exact copy of "Guid" with the exception of the Generics "<>" syntax . I just finished a multi million line project that pushed the limits of what could be done. and I could never have completed a project of that scale without making the compiler my best friend. What I just completed will be your every day working experience within a decade; I refrence Moore Law and Amdahl Law of systems growth and their impact on application size and complexity.

The "Guid<GuidTypes.TTT>" type is probably programming to a side effect and could probably be better implemented if the compiler team were involved. The technique could probably be applied to many types (int, long, float, ) to force the compiler to be your first Unit Test by using more specific typing. This is highly advantages in highly layered applications with numerous and complex associations between AOs, DTOs, and DOs in a business environment where expectations and demand will never be reasonable and mistake are guaranteed.

The reason the BCL should be extended is that the having a large GLOBAL solution in the project causes cross cutting code changes across the application for the sake of a technical deficit that could be better implemented once by Microsoft and then leveraged by millions of developers.
Jul 19, 2014 at 10:12 AM
You're right, I forgot to mention a fifth difference:
  • Lite<T> has two states, unloaded (default) and loaded. There are extension methods to retrieve the entity a Lite<T> references to myLite.Retrieve(), and you can cache the entity to avoid retrieving it the second time.
The fundamental idea stills the same: Lite<T> and Guid<T> are strongly-typed identifications. In fact, Lite<T> stated as ID<T> at the very begging but slowly evolved to Lite<T>.

Still, the devil is in the details.
  • What should be the id type? GUID? int? long? string?
  • Should there be a constraint on the T? You use enums... why not empty structs? or empty classes? We can serialize and transfer entities so we don't need DTOs and the entity itself is the obvious candidate.
  • Should this support inheritance in some way? For example, can we assign a Guid<Dolphin> in a Guid<Animal>. Using enums you make that impossible but fur us makes a lot of sense. BTW. That's what Lite<out T> is about. out is for covariance. That's why Lite<T> is implemented as an interface not a class.
  • Should they contain a catched ToString, so they can also be used in a ComboBox and returned in Autocomplete values, etc..?
  • Assuming there's an ORM somewhere... does it make sense to cache an already retrieved entity to avoid doing it the second time?
So, lot of complicated decisions that will make no one happy.

Also, while our solution is designed to integrate nicely with our ORM. Assuming you're in a classical distributed architecture with lots of SOAP/REST web-services... What's the point? There not going to be type fidelity in the JSON / WSDL proxies anyway.

The fundamental point is the value of Guid<T> as an standard. Having an standard implementation of string, DateTime, etc... makes interoperability between third party libraries much easier, but AFAIK no one is doing libraries of business object that you can easily integrate in your application (that's why we build Signum Framework) so I don't see what library in NuGet will start exposing and accepting Guid<T>.

Additionally, writing the class is really easy so your 'multi-million line projects' will only save a few hundreds :).

So, my point is that Guid<T> is an excellent pattern and I completely share your view of using the compiler as an ally as much as possible to build complex application. But since there are so many arbitrary decisions to be made, it's so easy to implement, and I don't really see the need for an standard because nobody is sharing domain objects, I think is better to implement the pattern in each application or in specific frameworks (EntityFramework?) and not in the BCL. I also don't see anything that the C# compiler can do to simplify the pattern.
Jul 22, 2014 at 3:49 AM
Olmo wrote:
You're right, I forgot to mention a fifth difference:
  • Lite<T> has two states, unloaded (default) and loaded. There are extension methods to retrieve the entity a Lite<T> references to myLite.Retrieve(), and you can cache the entity to avoid retrieving it the second time.
The fundamental idea stills the same: Lite<T> and Guid<T> are strongly-typed identifications. In fact, Lite<T> stated as ID<T> at the very begging but slowly evolved to Lite<T>.

Still, the devil is in the details.
  • What should be the id type? GUID? int? long? string?
  • Should there be a constraint on the T? You use enums... why not empty structs? or empty classes? We can serialize and transfer entities so we don't need DTOs and the entity itself is the obvious candidate.
  • Should this support inheritance in some way? For example, can we assign a Guid<Dolphin> in a Guid<Animal>. Using enums you make that impossible but fur us makes a lot of sense. BTW. That's what Lite<out T> is about. out is for covariance. That's why Lite<T> is implemented as an interface not a class.
  • Should they contain a catched ToString, so they can also be used in a ComboBox and returned in Autocomplete values, etc..?
  • Assuming there's an ORM somewhere... does it make sense to cache an already retrieved entity to avoid doing it the second time?
So, lot of complicated decisions that will make no one happy.

Also, while our solution is designed to integrate nicely with our ORM. Assuming you're in a classical distributed architecture with lots of SOAP/REST web-services... What's the point? There not going to be type fidelity in the JSON / WSDL proxies anyway.

The fundamental point is the value of Guid<T> as an standard. Having an standard implementation of string, DateTime, etc... makes interoperability between third party libraries much easier, but AFAIK no one is doing libraries of business object that you can easily integrate in your application (that's why we build Signum Framework) so I don't see what library in NuGet will start exposing and accepting Guid<T>.

Additionally, writing the class is really easy so your 'multi-million line projects' will only save a few hundreds :).

So, my point is that Guid<T> is an excellent pattern and I completely share your view of using the compiler as an ally as much as possible to build complex application. But since there are so many arbitrary decisions to be made, it's so easy to implement, and I don't really see the need for an standard because nobody is sharing domain objects, I think is better to implement the pattern in each application or in specific frameworks (EntityFramework?) and not in the BCL. I also don't see anything that the C# compiler can do to simplify the pattern.
I can see that you are interested in convincing me that the ORM you work with is the solution, but I am interested in a discussion about the language typing system. Specifically extending the strength of typing in a more restrictive and I believe positive way to impacts code quality and expressiveness when typing Class Properties, and Method Signatures. I can't do a thing for dynamic languages and there derived implementation like JSON, they have a use as different as a hammer is from a screwdriver.
Jul 22, 2014 at 7:43 AM
I'm comparing your Guid<T> with my Lite<T>. You asked in the firs place...
Could you post the code and its use as referenced to a Guid<T>?
Anyway, I think I've written valid concerns and if you still want that Guid<T> goes to the BCL an answer to anyone of those would be appreciated.
Jul 22, 2014 at 11:51 AM
What you are asking for is similar in concept to the F# units of measure functionality.

http://msdn.microsoft.com/en-us/library/dd233243.aspx

You can annotate floating point variables with units of measure annotations to prevent incorrect unit calculations being performed. In your case you would like to annotate a typed variable in such a way that only similarly typed variables with the same annotations are compatible.
Jul 22, 2014 at 6:12 PM
Edited Jul 22, 2014 at 6:16 PM
bradphelan wrote:
What you are asking for is similar in concept to the F# units of measure functionality.

http://msdn.microsoft.com/en-us/library/dd233243.aspx

You can annotate floating point variables with units of measure annotations to prevent incorrect unit calculations being performed. In your case you would like to annotate a typed variable in such a way that only similarly typed variables with the same annotations are compatible.
I have another thread "String<T>" that is similar to this (and different) and in that thread you suggested the topic of Dependent Type. The case of F# "units of measure" and Dependency Types are related and probably cohesive ideas. Do you think you could grace this thread with a comparison contrast to clarify this? I am impressed, and generally think you have far more competency then I do in language design. I am a language user that has leveraged everything C# offered and then found its limits and I think there are some opportunities to refine the product.

I see from the link http://msdn.microsoft.com/en-us/library/dd233243.aspx that F# has a compile time check on what I think are attributes. I avoid attributes for run-time solutions for what I believe are obvious performance and testing reasons, but attributes would work fine at compile time. I am not an F# expert, but I use functional capabilities in C# all the time. The F# "units of measure" would be gladly welcomed and used if I had it to use in C#. The business Logic layer and the rules would be much more expressive and if inteli-sense expressed it cleanly even better; much less out of date documentation needed :)

Can you tell me how it might be used to enforce differentiated Guids like "Guid<GuidTypes.Customer>" and "Guid<GuidTypes.Followup>"? Your comments are appreciated!
Jul 22, 2014 at 6:31 PM
Olmo wrote:
I'm comparing your Guid<T> with my Lite<T>. You asked in the firs place...
Could you post the code and its use as referenced to a Guid<T>?
Anyway, I think I've written valid concerns and if you still want that Guid<T> goes to the BCL an answer to anyone of those would be appreciated.
bradphelan just commented on the thread... He is flowing down a line of reasoning about the language and its expressiveness at compile time, and context based type checking of the model the language expresses. I am trying to help the C# team get a vision of lessons I learned in the field,and then adopt or extend those ideas. It is not really about any given implementation of a framework that supports an OPEN Source ORM. I looked at Signum, and can see you put a lot of work into it. We are just talking about and have different agendas. If you would like to open a different thread about Signum, I would be glad to discuss it and make contribution to problems you might have or ideas about extensions or anything else.
Jul 22, 2014 at 10:24 PM
We're supposed to speak here about language features. Signum Framework does not belong here. Guid<T> also looks like just a struct that doesn't require a language feature, but I don't know if the BCL team has a better forum.

Let's stop the unproductive discussion. How you feel about by five points?
Jul 31, 2014 at 8:48 PM
At least one person tried this in C# as a framework... I think the F# implementation is a typing or secondary typing system that has no run time existence; if I understand The MSDN documentation correctly.


Units of measure in C# - almost
Jul 31, 2014 at 11:03 PM
Units systems are really cool. They even understand operations. But I don't think they can play with generics.

Once you have Guid<T> you'll also want T Retrieve<T>(Guid<T> id)
Sep 15, 2014 at 11:23 PM
I also would like to see unit of measure support in the C# (and possibly VB, should I find myself working on a VB project in the future). As far as this topic goes, it appears a generic version of Guid was requested, for the simple purpose to prevent Guid values from arbitrarily being assigned from one domain to another. While this can be one method for preventing cross assignment of identifiers, it is not really what the generic type system was designed to do. (Also, instead of creating various empty enumeration types to be used as the type parameter, you could also have used the typename of the class containing the ID property, in this case, Guid<CustomerAO>, but I say this without knowing your project, as well as this is beyond the idea I am trying to get across.) Unit of measure support would also give this same level of support to prevent assigning a value of the same type but different domain, would work with any type without the need to modify the type, and is specifically built for this purpose (as well as a few other nice features such as automatic conversions of unit types, but they aren't being requested in the initial feature request).

As far as working with generics, I believe that when the idea of unit of measure was brought up, the question from the author had changed from asking to introduce the generic type of Guid to asking to introduce the concept of unit of measures for C#. Should unit of measures be supported, and should they be supported for more than just float and double (which is what F# appears to only support), then there would be no need for a generic Guid<T> type.

However, given that, in addition to an additional layer of type safety provided by units of measure, one of the main reasons for units of measure is automatic conversions between types, which is not really desired in most of the types of situations that the original suggestion describes. Thus, another possible suggestion would be to allow data stores (basically, properties, fields, variables, and anything else which acts as such) and expressions to be able to have a marker of some sort, in addition to a type, to allow the compiler to prevent assignment to a data store from an expression which has a different marker. I honestly don't know exactly what would happen when expressions start to get complex, as well as what may happen with "marker poisoning" (the idea that if an expression has a marker associated, to be combined with another expression, it too must have the same marker to be compatible), though it could be made that an expression with a marker cannot further be part of any other expression other than assignment, this concept may just get far to complicated. But I can see how some types of applications (especially those that work with some external data storage such as a database system) could benefit from this.