Seems really strange.
Seems really strange.
It's not a question of whether or not it's easy or hard; it's definitely technically doable.
It's a question whether or not it's desirable. I think the consensus is shifting decisively to saying that it _is_ desirable. The question then becomes how you can assert concepts on a type at compile time.
E.g. for a function add(a T, b T) and a type T, how do we assert that a + b is a valid statement? The draft design shows one approach to this.
And everyone not trying to make excuses for the Go-team knows what the one and only answer to that question is.
Interfaces are (abstract) types, so you can't “use interfaces instead of types”. Go’s use of interfaces is a variation of the “you can only have subtypes of abstract types” approach. Julia is an example of another modern language which also uses this general approach to type heirarchy, which, yes, does correspond to a strong preference for composition over inheritance; OTOH, like most relatively modern non-Go language with this approach, it also has generics. The idea tha generics are mostly used by languages that don't follow this approach is true in the sense that most languages, and therefore most with generics, do not follow this approach. But modern languages besides Go that follow the “composition-over-inheritance, no subtyping concrete types” approach seem to be just as likely as languages which support subtyping concrete types to have generics.
So, as well as not actually explaining anything (generics and composition-over-inheritance don't solve the same problem, so using the latter doesn't explain not using the former even if they did tend coincide in practice), it's doesn't seem to point to a real correlation.
Which is wrong.
>Go focuses on composition
Clearly false.
type Middleware interface {
Wrap(http.Handler) http.Handler
}
without requiring any generic types. For comparison, the same thing in Rust would rely on type polymorphism: //assuming that Handler is a trait
trait Middleware<T: Handler> {
type Result: Handler;
fn Wrap(handler: T) -> Result;
}
//NOTE: Not using impl-trait here to make it clear that
//two type variables are involved (one type parameter
//and one associated type).
You could mimic the Go behavior if you box all your traits, which Rust does not like to do because it's not zero-cost: trait Middleware {
fn Wrap(handler: Box<Handler>) -> Box<Handler>;
}Also, choosing which compilation strategy to use has a big impact on the compiler architecture (monomorphization? boxing? a mix of both?)
You can argue until the heat death of the universe about wether those priorities were chosen wisely, but it's irrelevant now. They built the tools for themselves focusing on what they found to be most important at the time. It's clearly worked out well enough for them and many of us like how Go turned out despite its short comings.
The only difference is that haskell generic functions can be specialized and the methods in the dictionary can be inlined: https://wiki.haskell.org/Inlining_and_Specialisation#What_is...