Type classes and implicit parameters are very similar in nature, although not quite the same. A polymorphic function with a type class constraint such as: sum :: Num a => [a] -> a can be intuitively treated as a function that implicitly accepts an instance of Num: sum_ :: Num_ a -> [a] -> a The instance Num_ a is essentially a record that contains the instance definition of Num a. (This is in fact how type classes are implemented under the hood by the Glasgow Haskell Compiler.) However, there is a crucial difference: implicit parameters are more
flexible; different instances of Num Int can be passed. In contrast, type classes enforce the so-called
coherence property, which requires that there should only be one unique choice of instance for any given type. The coherence property makes type classes somewhat antimodular, which is why orphan instances (instances that are defined in a module that neither contains the class nor the type of interest) are strongly discouraged. However, coherence adds another level of safety to a language, providing a guarantee that two disjoint parts of the same code will share the same instance. As an example, an ordered
set (of type Set a) requires a
total ordering on the elements (of type a) to function. This can be evidenced by a constraint Ord a, which defines a comparison operator on the elements. However, there can be numerous ways to impose a total order. Since set algorithms are generally intolerant of changes in the ordering once a set has been constructed, passing an incompatible instance of Ord a to functions that operate on the set may lead to incorrect results (or crashes). Thus, enforcing coherence of Ord a in this particular scenario is crucial. Instances (or "dictionaries") in
Scala type classes are just ordinary values in the language, rather than a completely separate kind of entity. While these instances are by default supplied by finding appropriate instances in scope to be used as the implicit parameters for explicitly-declared implicit formal parameters, that they are ordinary values means that they can be supplied explicitly, to resolve ambiguity. As a result, Scala type classes do not satisfy the coherence property and are effectively a
syntactic sugar for implicit parameters. This is an example taken from the Cats documentation: // A type class to provide textual representation trait Show[A] { def show(f: A): String } // A polymorphic function that works only when there is an implicit // instance of Show[A] available def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) // An instance for String implicit val stringShow = new Show[String] { def show(s: String) = s } // The parameter stringShow was inserted by the compiler. scala> log("a string") a string
Rocq (formerly named
Coq), version 8.2 onward, also supports type classes by inferring the appropriate instances. Recent versions of
Agda 2 also provide a similar feature, called "instance arguments". == Other approaches to operator overloading ==