We have traits at home (axelmagn.com)
49 points by Ygg2 a day ago
Panzerschrek a day ago
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.
Maxatar 13 hours ago
>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 but it's really just a trade-off. Without concepts you got long "stack-traces" of raw syntax errors that could dig deep into nested functions. With concepts you get long stack-traces of concept failures where concept A fails because concept B fails because concept C isn't satisfied and then you need to dig through this chain of concepts to figure out what's actually needed.
Traits in more principled languages like Haskell/Rust don't have this issue whatsoever because traits are semantic requirements in those languages while concepts in C++ just perform a "will it compile" kind of check.
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:
void serialize(std::ranges::contiguous_range auto&& range) {
// Serialize the underlying memory directly
}
void serialize(std::ranges::range auto&& range) {
// Serialize elements individually
}
The compiler selects the first overload for contiguous ranges such as std::vector and the second for non-contiguous ranges such as std::list.tialaramex a day ago
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.
mrkeen 13 hours ago
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.
cowlevel 12 hours ago
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.
dnautics 15 hours ago
> The only benefit of them seems to be slightly better error messages if someone misuses a template class/function.
Seems like a pretty valuable benefit?
indiosmo a day ago
Concepts are great for overload sets where previously you needed enable_if gymnastics. Also useful for if constexpr.
If you do template metaprogramming it makes your life a lot easier.
armchairhacker 13 hours ago
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.
Georgelemental 5 hours ago
> Definition: a Trait is a compile-time struct which contains related functions over a given type.
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/).
kooi 9 hours ago
Coming from Rust:
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; } });
axelmagn 8 hours ago
That was a mistake on my part. Thanks for catching. Fixed.
stock_toaster 13 hours ago
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.
mrkeen 13 hours ago
Complexity budget and all that. But I'd sooner throw out inheritance than traits.
detourdog 15 hours ago
First I had heard of duck-typing.
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.
s_tec 14 hours ago
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).
Jtsummers 14 hours ago
> isn't that dynamic inheritance? The seems like dynamic runtime.
Duck-typing can be implemented with static checks, and doesn't require inheritance to be involved at all.
zahlman 13 hours ago
It's ad-hoc inheritance (but really subtyping more generally).
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.
ZeroDayDreamer a day ago
[flagged]
no1guangming a day ago
Can't load the page