Add class templates (contracts or structural interfaces) to force consistent implementation the way interfaces do. #2679
-
An interface implements a contract between a class that implements it and its users, stating what public methods, properties, and events a user can expect. A template implements a contract between a class that implements it and classes that subclass it. It is like an interface, but it can demand a much wider scope of conditions and is intended to encourage streamlined implementation, and consistent design. A simple to explain example would be a template that specifies that its implementors must have a
More examples of how templates would work Think of an interface but:
[Addition by @gafter: These would be used in generic constraints.] |
Beta Was this translation helpful? Give feedback.
Replies: 48 comments
-
It looks like duck typing to me. |
Beta Was this translation helpful? Give feedback.
-
Seems to me something like a combination of traits, contracts. |
Beta Was this translation helpful? Give feedback.
-
Quack. |
Beta Was this translation helpful? Give feedback.
-
It's an interesting idea, but I think you left out something important in your proposal: this would only be useful as a generic constraint. For instance, a template that would enforce the presence of an addition operator:
Could be used like this:
|
Beta Was this translation helpful? Give feedback.
-
Sound similar to the Traits Idea ( #129) |
Beta Was this translation helpful? Give feedback.
-
"It can require that certain protected or internal members be present." What use is this? It seems like what an abstract type would do. |
Beta Was this translation helpful? Give feedback.
-
It is not clear from this proposal what context trait names could be used in the language. |
Beta Was this translation helpful? Give feedback.
-
Is it possible that some of the proposed features for Class Templates be implemented by the suggested enhancements to Interfaces? For instance, allowing Interfaces to declare Static Methods, Static Properties and Operator Overloads? I think that will generally keep things cleaner and not necessarily complicate the language. If I remember correctly, one of the Anders' mantras was to introduce new keywords in a language only as a last resort. However making interfaces support Statics will require updates to the CLR and many BCL classes. I see that as a good thing because it will enrich the entire CLR infrastructure (other languages benefit) without introducing too many moving parts and "concepts" that programmers have to learn afresh. Ultimately, the proposed benefits behind traits, mixins, and templates can get rolled up into one, if the implementation is done right. It will feel more like a natural/organic progression... |
Beta Was this translation helpful? Give feedback.
-
@gafter I think that would be just (static?) methods, since these "template" types should be resolved at compile-time (pretty much like inline functions in F# and compile-time resolved generics e.g. |
Beta Was this translation helpful? Give feedback.
-
Just a vote for offering the possibility under the hood to inline code and devirtualize calls using the actual types (i.e. This is one of the big use case of template in C++ (it generates tight optimized code, and you can even transfer functors -- but this one might be harder). |
Beta Was this translation helpful? Give feedback.
-
So, if I understand correctly, any delegate with the signature of
|
Beta Was this translation helpful? Give feedback.
-
@alrz yes, but only if traits are applicable to delegates as well. |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox As long as delegates translate to a special but yet, classes, we would have it for "free". Still, I think these so-called "traits" should be resolved at compile-time just like F#'s inline functions and member constraints, because compiler should know exactly what members are in question. This probably lessens portability of traits as proposed in this thread (implicitly implementing interfaces doesn't seem to work really, as discussed in the duplicated thread #7844 as well); likewise, you can't declare "inline methods" for classes in F#. As mentioned in #420, this needs CLR support; but as a consequence we would have "structurally equivalent delegates through traits" which I think brings a lot of value. |
Beta Was this translation helpful? Give feedback.
-
I tried to write my own very rough vision of how a trait/template should work: DefinitionsA trait is a new construct in C# language that can be used as a generic parameter type restriction. A trait has a visibility modifier. Traits can be either A trait has a name. Trait names should begin with an uppercase "T". A trait can inherit from other traits. A trait contains declarations of operations. Declarations of operations must have:
Operations can use the trait name as a type in either the return type or the parameter list. It is understood to be not an instance of any conforming type, but an instance of the type matching the restriction. Two operations are in conflict if they have the same name and the same types of the parameter list. It is an error to define two operations that are in conflict in a single trait. When a trait inherits from different traits operations that come in conflict, they either must be explicitly merged or at least one of them must be renamed. It is an error to merge traits that both have default implementations. It is allowed to define an operation that is in conflict with an operation defined in a parent trait if the new operation has an implementation and is marked with a Classes, interfaces and structs can conform to traits. To conform to a trait a class/struct/interface has to provide (potentially abstract) implementations of all operations defined in the trait and its parents. The implementations do not have to be members of the conforming type, but they must be visible to the trait. A field, a constant can serve as an implementation of a non-void operation with an empty parameter list. An indexer property of type T with N indices can serve as in implementation of: A static method can serve as an implementation of an operation with the same return type and parameter types. An instance method can serve as an implementation of an operation with the same return type and parameter types as its open delegate. A constructor of type T with N parameters can serve as an implementation of an operation with return type T and N parameters with the same types. Implementations must be explicitly mapped to the operations. When they have the same name, the mapping can be omitted. It is allowed to not map implementations to operations if operations have default implementations defined in terms of defined implementations. Already defined classes or structs can be declared to conform to traits. This is done via a Traits cannot have generic parameters 😢 until HKTs are implemented in the CLR. Traits and operations cannot have attributes (a temporary measure, I would love to mark and enforce transitivity, identity, closure and other properties of relations and operations). Some syntax examples
|
Beta Was this translation helpful? Give feedback.
-
@orthoxerox (1) The nice thing about extension methods is that they can be defined on generic parameters, I think traits should be able to be implemented on a generic parameter as well. (2) Your Anyway, I really liked Rust syntax for traits and it is very powerful btw. For example, utilizing #7671: trait TEquatable<T> {
static bool operator ==(T lhs, T rhs) => !(lhs!=rhs);
static bool operator !=(T lhs, T rhs) => !(lhs==rhs);
}
implement <T : IEquatable<T>> TEquatable<T> for T {
static bool operator ==(T self, T other) => self.Equals(other);
} This would allow to implement Traits as being discussed here are very similar to interfaces with static members (#2204), operators (#5624) and default methods (#258) plus a pluggable implementation (#3357). I wonder if these all could be merged together. |
Beta Was this translation helpful? Give feedback.
-
What should happen in the following case? where at least possible methods are applicable.
Does the |
Beta Was this translation helpful? Give feedback.
-
@AdamSpeight2008 see #7459 for an answer. |
Beta Was this translation helpful? Give feedback.
-
I have updated my draft to include the possibility of operations being mapped by name implicitly and fully implicit trait implementation. |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox I think you don't need a trait just for average. As far as I can tell, it would be something like this, interface IZero<T> { static T Zero { get; } }
interface IAdd<T, U, V> { V operator+(T, U); }
// etc
U Average<T, U>(IEnumerable<T> source)
where U : IZero<U>, IDiv<U,int,U>
where T : IAdd<U,T,U>
{
var sum = U.Zero;
var count = 0;
foreach(var item in source) {
count++;
sum += item;
}
return count > 0 ? sum / count : throw new Exception();
} Note that I'm using interfaces assuming that they can be plugged to other types like #3357. Also check out Rust's trait abstractions for primitives, in addition, it has default generic parameters (#6248), self type (#311) and associated types (no issue) which greatly help with the abstraction you had in mind. |
Beta Was this translation helpful? Give feedback.
-
@alrz That requires the Type of explicitly implement those interfaces. |
Beta Was this translation helpful? Give feedback.
-
@AdamSpeight2008 So these traits would be just compile-time constraints, right? |
Beta Was this translation helpful? Give feedback.
-
@alrz |
Beta Was this translation helpful? Give feedback.
-
@AdamSpeight2008 In that case, the whole method should get inlined, right? For example, class A { int P { get; } }
class B { int P { get; } }
trait T { int P { get; } }
int F<X>(X arg) where X : T => arg.P; In the last line there is no way for compiler to know what property should be emitted, unless in every call it get inlined with the client type. |
Beta Was this translation helpful? Give feedback.
-
It will know cos it going to be called on something. eg |
Beta Was this translation helpful? Give feedback.
-
@alrz Yes, if we add pluggable interfaces with static and non-virtual methods the interfaces become a lot like the traits I had in mind. The only remaining differences are:
|
Beta Was this translation helpful? Give feedback.
-
Just to chime in with another example of trait usage, here's one I've thought about before that has corollaries in Rust and Haskell:
This would let you write very simple parse code such as |
Beta Was this translation helpful? Give feedback.
-
Haven't seen this mentioned in this issue, but there's been some concrete work done on something very similar to this at the CaptainHayashi/roslyn fork. I've been going through the doc and it looks pretty amazing; seems like it would solve a whole slew of open issues all at once. I don't know if there is an issue dedicated to it though, or what the language design group's opinions on it are. |
Beta Was this translation helpful? Give feedback.
-
It reminds me concepts in C++17 only there it's done as part of templates instead of introducing another concept to the language. p.s. I really like the idea. :) |
Beta Was this translation helpful? Give feedback.
-
@masaeedu the issue with their approach is that it doesn't support generic types well. Their proposed changes to type inference might help, but getting direct support for HKTs in the CLR would be even better. |
Beta Was this translation helpful? Give feedback.
-
@eyalsk You've got the jist of the idea. |
Beta Was this translation helpful? Give feedback.
Sound similar to the Traits Idea ( #129)