orphan instance
A type-class instance for class C and type T which is defined outside of both:
- the module which defines C,
- and the module which defines T.
One frequent contributor to the appearance of orphan instances is importing the type and class from separate pre-packaged third-party libraries only to discover that neither provide an instance for that class/type pair. This is perfectly reasonable: the writers of libraries should not be expected to somehow explicate instances for every possible combination of their new class or type, with all existing and future types or classes!
So what exactly is the problem with orphan instances that thwarts this sensible, incremental approach?
The problematic "solution"
-- New braindead rules: export all instances.
- src/ExprE/hprexport.m, HBC 0.9994
All instances defined in a module A are imported automatically when importing A, or importing any module that imports A, directly or indirectly. This is because type class instances are special in that they don't have a name and cannot be imported explicitly:
-- this won't work... import Prelude (instance Eq(Bool))
This also means that they cannot be excluded explicitly:
-- ...neither will this import Prelude hiding (instance (Eq, Ord)(Int, Char, Float, Double))
Suppose you want to define an alternative instance to an existing instance. This is a bad thing, since if two instances for the same class/type pair are in scope, then you cannot describe in Haskell 98 which instance to use. If you want to use multiple instances for the same class/type, you have to ensure that they are never imported together in a module somewhere. It is almost impossible to assert that, or put differently, it would reduce the composability of libraries considerably.
Actually, non-orphan instances can avoid definition of multiple instances. For defining an instance you have to import the class and the type and then you will automatically have the according non-orphan instances imported, too. If you want to define a new instance then it will be rejected immediately.
The newtype wrap-around
Let's say you want an instance for Monoid Int that handles addition. Later on, you'd like to reuse that code for handling multiplication. But now, there's two instances of Monoid Int!
A common workaround is to wrap your type in a newtype and create an instance for it instead. Then, marshal (i.e. convert) between the original type and the new-instance type where necessary. In our example, we would have two such types: one for addition and one for multiplication. We would then create Monoid instances for each newtype wrapper.
Other potential solutions
- GHC currently checks for the use of overlapping orphan instances, not their (implict) imports.
- In his 2019 thesis, Scott Kilpatick provides another way to detect imports of overlapping orphan instances.
When orphan instances can be useful
It is worth noting that orphan instances can be viewed as a mechanism for writing modules of code with a fixed typed interface, but parameterized over the choice of implementation. In this case, orphan Instances act as a sort of plugin architecture for providing alternative implementations with a uniform interface.
A basic treatment of the relationship between type classes and modules (in the SML sense of modules) can be found in:
- http://www.mpi-sws.org/~dreyer/papers/mtc/main-short.pdf
- http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf
See also
- Multiple instances
- Libraries mailing list on Orphan instances can be good
- Ideas on possible compiler warnings for coping with orphan instances
- Libraries mailing list on Relaxing the PVP with regards to adding instances
- Partial Revelation feature of Modula-3 which causes similar problems like Haskell's type class instances
- Simple, safe multimethods in Racket which provides another explanation for the "module of origin" requirement for type-class instances.