The best way I’ve found to think of it: Type classes are filters.
Ord a => a -> a -> Ordering
can be read as “For all types a such that a is tagged with Ord, this is the definition.
This is a useful comparison. I’m in the position of knowing Haskell better than I know Java, so explanations aimed at “here’s what type classes do” miss the mark. It’s comparatively easy to see the similarities between the features, but the differences are a lot subtler…
return type polymorphism is its own class of neatness. Though I have a bit more reservations than before, being able to write return 1 and have the context do the “right” thing makes DSL writing quite nice.
I wonder how Rust traits and Swift protocols stack up to Haskell typeclasses. Swift protocols support a Self same-type constraint (point 1), and types can retroactively pick up conformance to protocols through extensions (point 2). I haven’t fully grokked point 3 yet, but it seems like Swift would need some level of higher-kinded types support (e.g. Self<A>) to properly emulate the CanFail example.