Let me start bold:
I perfectly understand the motivation of having it and can't understand the motivation of some people to argue against it...
What you want is nonvirtual ad hoc polymorphism.
- ad hoc: implementation can vary
- nonvirtual: for performance reasons; compiletime dispatch
The rest is sugar in my opinion.
C++ already has ad hoc polymorphism via templates. "Concepts" however would clarify what kind of ad hoc polymorphic functionality is used by which user defined entity.
C# just doesn't have any way to do it.
An approach that wouldn't be nonvirtual: If types like float would just implement something like "INumeric" or "IAddable" (...) we would at least be able to write a generic min, max, lerp and based on that clamp, maprange, bezier (...). However it wouldn't be fast. You dont want that.
Ways of fixing this:
Since .NET does JIT compilation anyway also generates different code for List<int>
than for List<MyClass>
(due to the differences of value and reference types) it probably wouldn't add that much of an overhead to also generate different code for the ad hoc polymorphic parts.
The C# language would just need a way to express it. One way is what you sketched up.
Another way would be to add type constraints to the function using an ad hoc polymorphic function:
U SuperSquare<T, U>(T a) applying{
nonvirtual operator (*) T (T, T)
nonvirtual Foo U (T)
}
{
return Foo(a * a);
}
Of course you could end up with more and more constraints when implementing Bar that uses Foo. So you may want a mechanism to give a name to several constraints that you regularly use... However this again is sugar and one way to approach it would be to just use the typeclass concept...
Giving a name to several constraints is like defining a type class, but i'd like to just look at it as some sort of abbreviation mechanism - sugar for an arbitrary collection of function type constraints:
adhoc AMyNeeds<T, U>
{
nonvirtual operator (*) T (T, T)
nonvirtual Foo U (T)
}
U SuperSquare<T, U>(T a) applying AMyNeeds<T, U>
{
return Foo(a * a);
}
At some place "main" all the type arguments are known and everything becomes concrete and can be compiled together.
What's missing still is the lack of creating different implementations. In the upper example we just used polymorphic functions and let everybody know...
Implementation then again could follow the way of extension methods - in their ability to add functionality to any class at any point:
public static class SomeAdhocImplementations
{
public nonvirtual int Foo(float x)
{
return round(x);
}
}
In main you now can write:
int a = SuperSquare(3.0f); // 3.0 * 3.0 = 9.0 rounded should return 9
The compiler checks all "nonvirtual" ad hoc functions, finds both a built-in float (*) operator and a int Foo (float)
and therefore is able to compile that line.
Ad hoc polymorphism of course comes with the downside that you have to recompile for each compile time type so that the right implementations get inserted. And probably IL doesn't support that being put into a dll. But maybe they work on it anyway...
I see no real need for instanciation of a type class construct.
If anything would fail on compilation we get the errors of the constraints or if those were boundled together with an "adhoc" codeclock the error message could get even more readable.
MyColor a = SuperSquare(3.0f);
// error: There are no ad hoc implementations of AMyNeeds<float, MyColor>
// in particular there is no implementation for MyColor Foo(float)
But of course also the instanciation of a type class / "adhoc polymorphism interface" is thinkable. The error message would then state: "The AMyNeeds constraint of SuperSquare has not been matched. AMyNeeds is available as StandardNeeds : AMyNeeds<float, int> as defined in MyStandardLib
".
It also would be possible to put the implementation in a class together with other methods and add the "adhoc interface" to the list of supported interfaces.
But independant of the particular language design: i don't see the downside of adding them one way or the other. Save statically typed languages will always need to push the boundary of expressive power, since they started by allowing too little, which tends to be a smaller set of expressive power a normal programmer would have expected to be possible...
tldr: i am on your side. Stuff like this sucks in mainstream statically typed languages. Haskell showed the way.