-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Please Add Type Classes to C# #16312
Comments
Neat. The additional method groups visible inside the method seems weird as does the generic arity when calling these from external code:
How do I call that algorithm? I would prefer something more explicit:
combined with better generic inference in cases where there is only one possible instance to choose from. Given an additional in scope:
I could call:
|
See also MattWindsor91/roslyn-concepts-issues#3 Should we use this as the master issue for all type class/trait/compile-time polymorphism issues? |
This is the master issue for whether or not we add this particular cluster of features approximately as described in the linked design documents. |
@bbarry That initial example should definitely be rejected since Implementing a concept for a new type instead of adding methods to existing types, the way Rust does for example, probably makes the most sense for C#. I like it 👍 |
Related issues (either about type classes (bold) or resolved by type classes or have to be taken into account during implementation (italics)):
|
Are
What does this mean? I can make an existing compiled type support a new interface?
Nod means same feature or something different? C++ one to me kind of sounds like compile-time method contracts #119. |
"С#" means C# vNow, "Concept C#" means "C# with three new keywords, implicit generic types and an additional step in the generic type inference algorithm". You can write type classes in C# vNow, but you'll have to specialize the types of every generic call.
It's not an interface, it's a concept/typeclass/trait. And yes, you can make an existing compiled type support a new concept using an instance/witness.
C++ doesn't use instances/witnesses, IIRC, it uses structural typing for its concepts (if the signature matches, the concept is implemented). I agree that "concept" might be a confusing name for the feature, but changing the keyword is the minormost issue. There are probably some really tricky backward compatibility issues with the new type inference algorithm that haven't cropped up yet. |
C++ concepts are not really method contracts as described in #119 because the contract is made between the argument and the parameter of a template, however, it is a form of Design by Contract. |
@orthoxerox @eyalsk thank you for explaining. |
Imho you shouldnt reuse generic syntax if it should function as traits or however it is called, since this is different than generic feature with exception to constraints (filtering classes based on traits might be useful). Remember that during compile time you can always transform new syntax to generic implementation. Such big transforms already happen in c# (if im not mistaken async is such an example). For now i dont have idea for syntax but i will try to come with something or link to already proposed syntaxes in the past for traits/however it is called. EDIT: #129 seems wasnt directly linked and i think is worth mentioning since this is another attempt on concept syntax. I dont like everything there (since most look like f#) but that can be revisited. |
@BreyerW What do you mean by "you shouldnt reuse generic syntax"? where it's reused? |
@eyalsk for example under Instance Inference on main page i see this:
there, in 3rd example, |
@BreyerW IMHO generic syntax is exactly the correct syntax to use here, because it's all about binding a concrete type from the caller that you can describe with bounds. Just like we already do with generics now. Generics are a suitable mechanism for all kinds of type constructors, so I'd be surprised if it used anything else. EDIT: Whoops, wrong @-mention! While I'm at it though, the thing I don't really like is introducing a new binding in the form of |
@BreyerW So you basically mean that
As far as I understand it you don't pass concepts, concepts are the types you model that are used as constraints for the generic type parameters, finally |
After hours of thinking i changed my mind. But i would like to see this proposal being more explicit than now, at least on definition side. TBH i had to reread some examples triple in order to understand whats going on. However im not satisfied with
looks like attributes too much imho. so maybe, just maybe different approach for more explicitness? like:
explicit call would look like:
under the hood it still would be generics, just compiler had to transform this to desired implementation. |
@BreyerW the idea behind instances is that they link together an existing type and a concept. Implicit concepts are a good idea, especially since most types will have a single instance per concept. Integers have at least two common rings defined on them, so I wouldn't mind being able to pick one specific instance when passing an int to something that applies it to itself using the operation of the ring. |
You're thinking that changing symbols to words implies explicitness but this doesn't make it so, it just makes it more verbose besides this doesn't seems like idiomatic C#.
I wouldn't want to write this. |
If that so then i guess it should be discarded. I have question though: do we really need two new keywords? I mean current |
@BreyerW to simplify the cognitive load on the programmer and the compiler. A concept is implemented as an interface, but no real type implements it. If you see this is not an ordinary interface, you immediately realize when writing your Similarly, the compiler can take a shortcut in the specialized type inference routine when it knows the type parameter requires an instance and not a type. |
This is really great work, and it's pretty straightforward. I think there is a strong use case for solving this problem that every C# developer eventually bumps up against.. I hope this gets serious consideration. |
The implicit type is nice but it's weird that it looks unused. Instead, how about reusing the public static void qsort<T>(T[] arr, int a, int b)
where implicit : IOrd<T> {
...
while (implicit.Compare(arr[i], x) < 0) i++;
while (Compare(x, arr[j]) < 0) j--; // implied, equivalent to `implicit.Compare(...)`
...
} * (or perhaps |
And for two or more concepts, bringing their members into scope could become ambiguous, requiring named implicit types: public concept ISquare<T> {
T Calculate(T x);
}
public concept ICube<T> {
T Calculate(T x);
}
instance IntSquare : ISquare<int> {
public int Calculate(int x) => x * x;
}
instance IntCube : ICube<int> {
public int Calculate(int x) => x * x * x;
}
public static (A, B) SquareCube<A, B>(A a, B b)
where implicit SquareA :ISquare<A>
where implicit CubeB : ICube<B>{
...
var aSquared = SquareA.Calculate(a);
var bCubed = CubeB.Calculate(b);
return (aSquared, bCubed);
...
} Named implicit types could also work in the single-concept case, and thus it may not be worth using the syntax in my previous comment. Then again, I presume that single-concept generic methods would be used much more often and the shorter syntax may be justified. |
I like having the concept in the generic bindings, like it currently is in the concept samples: static A M<A, implicit NumA>(A x) where NumA : Num<A> I'd also support making static A M<A, NumA>(A x) where NumA : Num<A> Couple that with generic type inference and you could define something like: public concept Square<T>
{
T Calculate(T x);
}
instance SquareInt : Square<int>
{
public int Calculate(int x) => x * x;
}
public static T SquareValue<SquareT, T>(T a)
where SquareT : Square<T>
{
...
var aSquared = SquareT.Calculate(a);
...
}
// Full inference
SquareValue(x);
// Partial inference
SquareValue<SquareInt>(x);
// No inference
SquareValue<SquareInt, int>(x); Then if we had multiple instance of // Error: cannot infer type for SquareT
SquareValue(x);
// Partial inference
SquareValue<SquareInt>(x);
SquareValue<FastSquareInt>(x);
// No inference
SquareValue<SquareInt, int>(x);
SquareValue<FastSquareInt, int>(x); |
Is it possible to have anonymous concept at generic constraint? |
@Thaina that would be structural typing a la cpp concepts, not type classes. I guess it's possible, but is more or less a different feature. |
@orthoxerox I don't know if it's a different feature, I'd say it just a different approach, I like the proposed feature though. |
@orthoxerox @eyalsk I just want to close my #9595 request. If public void SetPosition<T>(T pos) where T : concept{ float X,Y,Z; }
{
x = pos.X;
y = pos.Y;
z = pos.Z;
} |
@Thaina |
But didn't we already have interfaces? interface Functor for F<A> { // concept syntax
public F<B> FMap<A, B>(this F<A> obj, Func<A, B> mor);
}
Functor<List> {
FMap(obj, mor) = obj.Map(mor)
}
interface Applicative for F<A> where F : Functor { // concept syntax with constraints
static F<A> Pure<A>(A obj);
public F<B> Ap<A, B>(this F<A> obj, F<Func<A, B>> mor);
}
interface Show { // convintinal interface
String ShowAsString()
}
String describe<A> (F<A> obj) where F : Functor, A : Show {
return obj.FMap((a) => a.ShowAsString())
} |
@be5invis Could you explain the difference between your proposal and the OP in more detail, please? |
@orthoxerox I am replying some other replies introduces too many keywords. |
This means that in the future, when I create a new type which is comparable, besides having to implement:
Since we already have:
Instead of having the idea of "adding concept", what about adding C++'s partial instantiation (without adding dependent type)? And then we have all the power from concept. |
@orthoxerox You referenced my issue #11773 as being solved by type classes in #16312 (comment), could you please explain how type classes solve this? Perhaps I'm missing something, but I don't think they do. Particularly and specifically, #11773 allows functionality (a method implementation) in the base to be specialised based on the type of the inheriting type. For a concise example outside of the code dump in the original issue see #11773 (comment) and note |
@TheOtherSamP |
@orthoxerox Okay, I understand where you're coming from. I don't think this is really the same thing though as it needs to be implemented each time. That's fairly different from a non virtual method implemented in the base. Thank you for clarifying though, much appreciated. |
We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages. Type classes are under consderation at dotnet/csharplang#110 |
See
The text was updated successfully, but these errors were encountered: