wiki

Orphan instance

An instance is an orphan when the module declaring it owns neither the class nor the type[1]. In Haskell, type class instances are global: once a module defining an instance is anywhere in a program, the instance is visible to every module that imports it, directly or indirectly, and there is no way to hide it[2]. Code that relies on classes such as Ord assumes global uniqueness, that each class has at most one instance for each type in the whole program[3]. An instance defined with its class or its type cannot be duplicated without a compile error, but two orphans for the same pair, in two unrelated libraries, can both compile, and a program that links both may use one in one place and the other in another. GHC warns about orphans with -Worphans; Rust's coherence rules forbid them[4].

§ 01

Example

The fix is the newtype, which makes the choice local and explicit rather than global and ambient.

-- orphan: neither Ord nor Text is ours
instance Ord Text where compare = compareCaseInsensitively
-- not an orphan: we own CI, so the instance travels with it
newtype CI = CI Text
instance Ord CI where
compare (CI a) (CI b) = compareCaseInsensitively a b

The first instance can be defined by any library, and any other library can define a different one. The second can only be defined in the module that declares CI, or in the module that declares Ord, so there is exactly one.

§ 02

Why orphans break things

The problem is a mismatch in scoping: instance resolution is global and implicit, but imports are local and explicit. GHC checks for duplicate instances lazily, only when a module actually needs to resolve a constraint for which two instances are visible[1][5]. Two modules can therefore each define an orphan Ord instance for the same type, each compile against its own, and export functions whose behavior depends on it. A program that imports both compiles as long as it never asks for the instance itself.

baseclass OrdTypesdata VersionCataloginstance Ord VersionQueryinstance Ord VersionMainimports bothbuilds the Set by(major, minor)searches it by(minor, major)same Set
Catalog and Query each define an orphan Ord Version. Main imports both and passes a Set built under one ordering to a function that searches it under the other; since Main never resolves Ord Version itself, nothing is reported.

A type with no Ord instance.

module Types (Version (..)) where
-- A type from one library, with no Ord instance of its own.
data Version = Version Int Int deriving (Eq, Show)

One library orders versions by (major, minor) and builds a Set.

module Catalog (catalog) where
import qualified Data.Set as Set
import Types
-- Orphan: this module owns neither Ord nor Version.
instance Ord Version where
compare (Version a b) (Version c d) = compare (a, b) (c, d)
catalog :: Set.Set Version
catalog = Set.fromList [Version m n | m <- [1 .. 3], n <- [0 .. 3]]

Another orders them by (minor, major) and searches a Set.

module Query (available) where
import qualified Data.Set as Set
import Types
-- A second orphan for the same pair, ordering by minor version first.
instance Ord Version where
compare (Version a b) (Version c d) = compare (b, a) (d, c)
available :: Version -> Set.Set Version -> Bool
available = Set.member

A program using both.

import qualified Data.Set as Set
import Catalog (catalog)
import Query (available)
import Types
-- Main sees both instances but never resolves Ord Version itself, so GHC
-- does not report the conflict.
main :: IO ()
main = do
print (Set.size catalog)
print [ (m, n) | m <- [1 .. 3], n <- [0 .. 3], not (available (Version m n) catalog) ]

Running it: the set has 12 elements, and half of them cannot be found.

12
[(2,0),(2,1),(2,2),(3,0),(3,1),(3,2)]

Data.Set is a balanced search tree. Its invariant, that the elements are sorted by the Ord instance, was established under one instance and is being relied on under another, so a lookup follows the wrong branches. No exception is raised and nothing in the types reveals it. The same happens with Map keys, Hashable instances and hash maps, and any structure that stores an invariant computed from a class method.

If the program resolves the instance itself, GHC does report the conflict.

The same lookup, written in a module that sees both instances.

import qualified Data.Set as Set
import Catalog (catalog)
import Query (available)
import Types
main :: IO ()
main = print (Set.member (Version 2 0) catalog)

GHC's error.

Direct.hs:7:15: error:
* Overlapping instances for Ord Version
arising from a use of `Set.member'
Matching instances:
instance Ord Version -- Defined at Query.hs:7:10
instance Ord Version -- Defined at Catalog.hs:7:10
* In the first argument of `print', namely
`(Set.member (Version 2 0) catalog)'
In the expression: print (Set.member (Version 2 0) catalog)
In an equation for `main':
main = print (Set.member (Version 2 0) catalog)
|
7 | main = print (Set.member (Version 2 0) catalog)
| ^^^^^^^^^^
§ 03

Coherence and global uniqueness

Two properties are involved[3][5]. Coherence says that every valid typing of a program has the same meaning, so the choice of instance at a given use does not depend on how the type checker happened to derive it. Global uniqueness says that there is at most one instance of a class for a type in the entire program. Haskell's design guarantees coherence only given global uniqueness, and libraries such as containers depend on global uniqueness for correctness: a Set a does not carry its ordering, but assumes that the Ord a at every later use is the one it was built with. Orphans are the way global uniqueness fails without the compiler noticing.

§ 04

Warnings and costs

The warning, reported where the orphan is defined.

ghc -Worphans Catalog.hs

GHC's output.

Catalog.hs:7:1: warning: [-Worphans]
Orphan instance: instance Ord Version
Suggested fix:
Move the instance declaration to the module of the class or of the type, or
wrap the type with a newtype and declare the instance on the new type.
|
7 | instance Ord Version where
| ^^^^^^^^^^^^^^^^^^^^^^^^^^...

Orphans also cost compile time. For an ordinary instance, GHC can find it by loading the interface of the module that defines the class or the type, which it needs anyway. An orphan could be anywhere, so GHC records every orphan module in the interface files of the modules that depend on it and loads all of them whenever it compiles a module that might need one[1].

§ 05

Avoiding orphans

The standard fix is a newtype. Wrapping the type gives the instance a type of its own, defined in the same module, so the instance is not an orphan and cannot be duplicated. The two orderings become two types, and the compiler stops a set built under one from being searched under the other.

Two orderings as two newtypes.

import qualified Data.Set as Set
data Version = Version Int Int deriving (Eq, Show)
-- Each ordering is a newtype owned by the code that wants it, so the
-- instance travels with the type and there is only ever one per type.
newtype ByRelease = ByRelease Version deriving (Eq, Show)
newtype ByMinor = ByMinor Version deriving (Eq, Show)
instance Ord ByRelease where
compare (ByRelease (Version a b)) (ByRelease (Version c d)) = compare (a, b) (c, d)
instance Ord ByMinor where
compare (ByMinor (Version a b)) (ByMinor (Version c d)) = compare (b, a) (d, c)
main :: IO ()
main = do
let versions = [Version m n | m <- [1 .. 3], n <- [0 .. 3]]
releases = Set.fromList (map ByRelease versions)
-- Set ByRelease and Set ByMinor are different types, so a set built under
-- one ordering cannot be searched under the other.
print (all (\v -> Set.member (ByRelease v) releases) versions)
print (take 3 (Set.toList (Set.fromList (map ByMinor versions))))

Running it.

True
[ByMinor (Version 1 0),ByMinor (Version 2 0),ByMinor (Version 3 0)]

Other options are to contribute the instance upstream, to the package that defines the class or the type, or to put a widely needed orphan in a single package that everyone agrees to use; packages such as quickcheck-instances collect instances of one library's classes for other libraries' types in this way. GHC's DerivingVia reduces the boilerplate of newtypes by deriving the instance from another type's representation.

§ 06

In other languages

Rust enforces coherence statically with its orphan rule: an impl of a trait for a type is allowed only in the crate that defines the trait or the type, with some relaxations for local types appearing as type parameters[4]. This keeps global uniqueness but means that a program cannot implement a trait from one library for a type from another at all; the newtype pattern is the standard workaround. Scala's implicits and given instances are lexically scoped rather than global, so there is no global uniqueness to break, but a sorted collection must then store its ordering, or risk the same mismatch[6]. ML functors pass the comparison explicitly, as in OCaml's Set.Make, whose result type records which ordering was used[7].

§ 07

History

Wadler and Blott's type classes of 1989 assumed a single instance per class and type[2]. The Haskell reports did not restrict where instances may appear, and the problems of overlapping and duplicated instances were among the design questions surveyed by Peyton Jones, Jones and Meijer in 1997[5]. GHC introduced the orphan warning and its interface-file bookkeeping for orphan modules as the Haskell package ecosystem grew[1]. Rust adopted its orphan rule with the language's trait coherence design, and RFC 1023 in 2015 gave it its present form[4].

see also

further reading

  1. [1]GHC User’s Guide, “Orphan modules and instance declarations” and the -Worphans warning.
  2. [2]P. Wadler, S. Blott, “How to make ad-hoc polymorphism less ad hoc”, POPL (1989).
  3. [3]E. Z. Yang, “Type classes: confluence, coherence and global uniqueness”, blog post (2014).
  4. [4]Rust RFC 1023, “Rebalancing coherence” (2015).
  5. [5]S. Peyton Jones, M. Jones, E. Meijer, “Type classes: an exploration of the design space”, Haskell Workshop (1997).
  6. [6]B. C. d. S. Oliveira, A. Moors, M. Odersky, “Type classes as objects and implicits”, OOPSLA (2010).
  7. [7]D. Dreyer, R. Harper, M. M. T. Chakravarty, G. Keller, “Modular type classes”, POPL (2007).

last updated