3 ms·
The difference with Rust is that you can apply a trait to an extant type you don't "own" - you can't do that in Java or C#: you would need to use the decorator-
by DaiPlusPlus 9y ago
The difference with Rust is that you can apply a trait to an extant type you don't "own" - you can't do that in Java or C#: you would need to use the decorator-pattern (boilerplate ahoy!). C++'s checks are closer to duck-typing in practice.
- pjmlp 9y agoI agree with you in regard to Java and C#, not so much regarding C++, because GCC already supports concepts since version 6.0, with other compilers adding support for them as part of the validation work to have them finally on ANSI C++20 standard. In any case, regardless of the fine grain details, all of them allow for to "specify the generic constraints explicitly.".
- louthy 9y agoActually you can do ad-hoc polymorphism in C# (and I assume Java too, although I've not used it). Here's an example of implementing an Eq and Ord 'type class' in C#, with 'class instances' of OrdInt and OrdString. Then a simple generic bubble-sort function which works on any type that has an Ord instance: public interface Eq<A> { bool Equals(A x, A y); } public interface Ord<A> : Eq<A> { bool GreaterThan(A x, A y); bool GreaterThanOrEq(A x, A y); bool LessThan(A x, A y); bool LessThanOrEq(A x, A y); } public struct OrdInt : Ord<int> { public bool Equals(int x, int y) => x == y; public bool GreaterThan(int x, int y) => x > y; public bool GreaterThanOrEq(int x, int y) => x >= y; public bool LessThan(int x, int y) => x < y; public bool LessThanOrEq(int x, int y) => x <= y; } public struct OrdString : Ord<string> { public bool Equals(string x, string y) => x == y; public bool GreaterThan(string x, string y) => x.CompareTo(y) > 0; public bool GreaterThanOrEq(string x, string y) => x.CompareTo(y) >= 0; public bool LessThan(string x, string y) => x.CompareTo(y) < 0; public bool LessThanOrEq(string x, string y) => x.CompareTo(y) <= 0; } public static class Testing { public static void Test() { var x = new[] { 3, 8, 1, 2, 10 }; var y = new[] { "mary", "had", "a", "little", "lamb" }; BubbleSort<OrdInt, int>(x); BubbleSort<OrdString, string>(y); } public static void BubbleSort<OrdA, A>(A[] values) where OrdA : struct, Ord<A> { bool swap; do { swap = false; for (var i = 0; i < values.Length - 1; i++) { var x = values[i]; var y = values[i + 1]; if (default(OrdA).GreaterThan(x, y)) // Ad-hoc polymorphic call to GreaterThan { swap = true; values[i] = y; values[i + 1] = x; } } } while (swap); } }
- DaiPlusPlus 9y agoBut you just proved my point: your OrdString and OrdInt types there are implementations of the Decorator pattern (just using interfaces instead of superclasses).
- louthy 9y agoNo they're not, they are not wrapping an object instance, they are providing ad-hoc functionality only. The generic parameters provided to BubbleSort are constraining the arguments and allowing access to the ad-hoc functionality, something that can't be done with inheritance unless you own the type. This is exactly how traits, implicits, type-classes, class-instances, and concepts work in other languages like Scala, Haskell, etc.