I see little to no benefits of introducing such "traits" into a language having duck-typing in templates. It may sometimes improve code readability (slightly), but with more boilerplate code for traits handling in exchange. And the result machine code will be identical to code with no traits at all (with only duck-typing).
C++ is a good example of this. It had duck-typing in templates for decades. Then some sort of trait-like mechanism named concepts has been introduced. The only benefit of them seems to be slightly better error messages if someone misuses a template class/function.
I'm not a Zig expert but it looks as though the intent is that duck-typing is not a core part of this idea. Having traits allows Carol to implement Ben's Teleport trait for Alice's Goose.
With duck-typing Carol needs to either prevail upon Alice to make you a teleporting Goose, or make her own "CarolsTeleportingGoose" wrapper type which implements the teleporting functions.
Traits/typeclasses tend to disallow your three-part scenario. It's called the orphan instance problem.
The "Goose teleports" code needs to go in Ben/Teleport.src, or Alice/Goose.src. Otherwise the compiler doesn't know which implementation to use if there are multiple.
Scala (and I think Ocaml) have mechanisms for selecting which one to use. In Haskell you'd use a newtype wrapper.
The benefit to C++ is that it’s actively trying for as many features as possible, keeping out of reach full support from any compiler. Maybe GPT-8 will be the first true C++ expert, built by the first superintelligence GPT-7.
>The only benefit of them seems to be slightly better error messages if someone misuses a template class/function.
The error messages are arguably worse with concepts than without them.
The primary benefit of concepts in C++ is overload resolution, they allow you to write multiple functions with the same name, and same number of parameters, but where the types of the parameters are required to satisfy certain properties.
Concepts are predicates for types, that is, functions from types to booleans. Everyone was already defining templates like is_contiguous_range<T>::value so much that they decided to give it first-class status in the language.
The really cool part about concepts (unless you're writing the compiler) is actually requires expressions, which can replace a lot of SFINAE.
In Zig's case, it's compile time. An expression like `foo.bar(1, 2)` can only compile if `foo` has a method named `bar` with the right number of arguments. In a duck-typed system, `foo` can be any type of value you like, as long as it "fits" the shape.
In contrast, many languages require you to describe the shape up-front, and then constrain `foo` to implement that shape. For instance, in Rust we would have to write something like `trait Bar { fn bar(a: u32, b: 32); }`, and then say something like `foo: T where T: Bar`, before we're allowed to call the `bar` method.
Both are compile-time safe, but one requires more ceremony (but is more explicit), while the other is simpler (but you might have to guess the requirements).
While a well written article, and certainly intellectually interesting, I'm not sure I'd be excited to run across this level of indirection in a codebase if I were debugging an issue.
Why is does implementation still have a the generic T? Should it not be concrete on i32?
/// i32 forms a sum reducer under 0 and addition
const sum_trait = impl(Reducer(i32), struct {
pub const empty = 0;
pub fn combine(x: T, y: T) T { return x + y; }
});
20 comments
[ 0.47 ms ] story [ 8.4 ms ] threadC++ is a good example of this. It had duck-typing in templates for decades. Then some sort of trait-like mechanism named concepts has been introduced. The only benefit of them seems to be slightly better error messages if someone misuses a template class/function.
If you do template metaprogramming it makes your life a lot easier.
With duck-typing Carol needs to either prevail upon Alice to make you a teleporting Goose, or make her own "CarolsTeleportingGoose" wrapper type which implements the teleporting functions.
The "Goose teleports" code needs to go in Ben/Teleport.src, or Alice/Goose.src. Otherwise the compiler doesn't know which implementation to use if there are multiple.
Scala (and I think Ocaml) have mechanisms for selecting which one to use. In Haskell you'd use a newtype wrapper.
Seems like a pretty valuable benefit?
The error messages are arguably worse with concepts than without them.
The primary benefit of concepts in C++ is overload resolution, they allow you to write multiple functions with the same name, and same number of parameters, but where the types of the parameters are required to satisfy certain properties.
For example:
The compiler selects the first overload for contiguous ranges such as std::vector and the second for non-contiguous ranges such as std::list.The really cool part about concepts (unless you're writing the compiler) is actually requires expressions, which can replace a lot of SFINAE.
https://en.wikipedia.org/wiki/Duck_typing
After reading the page I was left with the question of isn't that dynamic inheritance? The seems like dynamic runtime.
In contrast, many languages require you to describe the shape up-front, and then constrain `foo` to implement that shape. For instance, in Rust we would have to write something like `trait Bar { fn bar(a: u32, b: 32); }`, and then say something like `foo: T where T: Bar`, before we're allowed to call the `bar` method.
Both are compile-time safe, but one requires more ceremony (but is more explicit), while the other is simpler (but you might have to guess the requirements).
The dynamically typed version is dynamic inheritance, or really just dynamic attribute lookup.
The statically typed version is "generic programming" with templates. See for example the way that C++ standard library sort works.
Why is does implementation still have a the generic T? Should it not be concrete on i32?
/// i32 forms a sum reducer under 0 and addition const sum_trait = impl(Reducer(i32), struct { pub const empty = 0; pub fn combine(x: T, y: T) T { return x + y; } });
There is actually a proposal to make Rust traits desugar behind the scenes to basically this: https://goals.rust-lang.org/2026/dictionary-passing-style-ex...
Rust's creator originally wanted an OCaml-style module system instead of traits (https://graydon2.dreamwidth.org/307291.html), and interest persists today (https://www.boxyuwu.blog/posts/an-incoherent-rust/).