wiki

Deriving strategy

A deriving clause asks the compiler to write an instance. Haskell 98 allowed this for a fixed set of classes, such as Eq, Ord and Show, and generated code from the constructors of the type[4]. GHC added further mechanisms: generalized newtype deriving reuses the instance of the type a newtype wraps, DeriveAnyClass writes an empty instance that relies on default methods, often generic ones[5], and DerivingVia reuses the instance of any type with the same representation[1]. For a newtype, more than one of these usually applies and they give different instances, so GHC 8.2 added the DerivingStrategies syntax to name the one intended[2]. The coercing strategies are free at run time, and their safety rests on type roles[3].

§ 01

Example

The four strategies on two newtypes.

{-# LANGUAGE DerivingStrategies, GeneralizedNewtypeDeriving, DeriveAnyClass, DerivingVia, DefaultSignatures #-}
import Data.Monoid (Sum (..), Product (..))
-- A class with a default method, for anyclass deriving.
class Describe a where
describe :: a -> String
default describe :: Show a => a -> String
describe x = "<" ++ show x ++ ">"
newtype Age = Age Int
deriving stock (Show, Eq) -- structural: shows as Age 3
deriving newtype (Num, Ord) -- Int's instances, by coercion
deriving anyclass Describe -- the class's default methods
deriving (Semigroup, Monoid) via (Sum Int) -- Sum Int's instances
newtype Score = Score Int
deriving newtype Show -- Int's Show: shows as 3
deriving (Semigroup, Monoid) via (Product Int)
main :: IO ()
main = do
print (Age 3, Score 3)
print (Age 3 + 4, Age 3 < Age 4)
putStrLn (describe (Age 3))
print (mconcat [Age 1, Age 2, Age 3], mempty :: Age)
print (mconcat [Score 2, Score 3, Score 4], mempty :: Score)

Running it.

(Age 3,3)
(Age 7,True)
<Age 3>
(Age 6,Age 0)
(24,1)

The distinction is not cosmetic. stock Show prints Age 3 and newtype Show prints 3; via (Sum Int) makes <> addition while via (Product Int) makes it multiplication, from types with the same representation.

§ 02

The four strategies

instance C Agenewtype Age = Age Intstockcode generated from the constructorsShow: Age 3newtypeInt's dictionary, coerced to AgeShow: 3anyclassan empty instance: the class defaultsDescribe: <Age 3>via Sum IntSum Int's dictionary, coerced to AgeMonoid: additionthe two coercing strategies need the roles to allow Int to become Age
The four ways GHC can produce an instance for a newtype. Stock and anyclass write new code; newtype and via reuse an existing instance by coercing its dictionary, which costs nothing at run time.

Stock

The compiler generates code by structural recursion over the constructors, as the Haskell report specifies for Eq, Ord, Enum, Bounded, Show, Read and Ix[4]. GHC extends stock deriving to Functor, Foldable, Traversable, Generic, Data and Lift.

A small type.

module Stock where
data Shape = Circle Double | Rect Double Double deriving Eq

The Eq instance GHC generates, as printed by -ddump-deriv.

Derived class instances:
instance Eq Shape where
(==) a b
= case
(case (dataToTag# a) of
a# -> case (dataToTag# b) of b# -> a# /=# b#)
of
1# -> False
_ -> case a of
(Circle a1) -> case b of (Circle b1) -> ((a1 == b1))
(Rect a1 a2)
-> case b of (Rect b1 b2) -> (((a1 == b1)) && ((a2 == b2)))

The generated code first compares constructor tags, then the fields of equal constructors, which is what a careful hand-written instance would do.

Newtype

For newtype Age = Age Int, generalized newtype deriving reuses Int's dictionary. A newtype has the same representation as the type it wraps, so the Num Int dictionary can be coerced to a Num Age dictionary without generating any code, and the derived methods run exactly as fast as Int's.

Anyclass

The instance is empty, so every method takes the class's default. This is useful for classes whose defaults are defined generically, through Generic and DefaultSignatures, as in serialization libraries: deriving anyclass (ToJSON) gives a JSON encoder computed from the type's generic representation[5].

Via

deriving C via V reuses V's instance for any V with the same representation as the type being defined[1]. The Sum and Product wrappers exist to select monoids in this way, and libraries define newtypes whose only purpose is to carry an instance to be borrowed: a type can derive Semigroup via Max Int, Show via a wrapper that prints in hexadecimal, or Num for a tuple via an applicative lifting. Blöndal, Löh and Scott argued that this turns many hand-written instances into a single line[1].

§ 03

Ambiguity

Without an explicit strategy, GHC uses stock deriving for the standard classes and otherwise chooses by the extensions in effect. When both GeneralizedNewtypeDeriving and DeriveAnyClass are on, a newtype deriving a class with defaults could use either[2].

Two applicable strategies with different results.

{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveAnyClass #-}
class Describe a where
describe :: a -> String
describe _ = "something"
instance Describe Int where
describe n = "the number " ++ show n
-- Both newtype deriving and anyclass deriving apply to Describe.
newtype Age = Age Int deriving Describe
main :: IO ()
main = putStrLn (describe (Age 3))

GHC's warning.

ambiguous.hs:10:32: warning: [-Wderiving-defaults]
* Both DeriveAnyClass and GeneralizedNewtypeDeriving are enabled
Defaulting to the DeriveAnyClass strategy for instantiating Describe
* In the newtype declaration for `Age'
Suggested fix:

Running it: the class default, not Int's instance.

something

Writing deriving newtype Describe or deriving anyclass Describe makes the choice explicit and silences the warning.

§ 04

Roles and soundness

The coercing strategies are sound only if every method of the class can be coerced from the wrapped type to the newtype. That fails when a method mentions the type under a type constructor whose invariants depend on the exact type. Set Int and Set Age have the same representation, but a set's ordering comes from its element type's Ord instance, so coercing one to the other could break the tree's invariant. GHC tracks this with roles: Set's parameter has the nominal role, and a coercion that changes it is rejected[3].

A class whose method mentions Set a.

{-# LANGUAGE DerivingStrategies, GeneralizedNewtypeDeriving #-}
import qualified Data.Set as Set
class Contains a where
contains :: Set.Set a -> a -> Bool
instance Contains Int where
contains s x = Set.member x s
-- Coercing Set Int -> Bool to Set Age -> Bool is not allowed: Set's
-- parameter has the nominal role, because its invariant depends on Ord.
newtype Age = Age Int deriving newtype Contains

GHC's error.

roles.hs:12:40: error:
* Couldn't match type `Int' with `Age'
arising from the coercion of the method `contains'
from type `Set.Set Int -> Int -> Bool'
to type `Set.Set Age -> Age -> Bool'
* When deriving the instance for (Contains Age)
|
12 | newtype Age = Age Int deriving newtype Contains
| ^^^^^^^^

Before roles, generalized newtype deriving could be combined with type families to coerce between unrelated types, breaking type safety. Roles, introduced in GHC 7.8 in 2014, closed that hole by distinguishing nominal, representational and phantom parameters[3][6].

§ 05

In other languages

Rust's #[derive] attributes correspond to stock deriving, and procedural macros let libraries add derivable traits, much like anyclass deriving through generics. OCaml has no built-in deriving; ppx preprocessors such as ppx_deriving generate the code from type declarations at compile time. Neither language has an equivalent of newtype or via deriving, since neither has zero-cost coercions between a type and its wrapper that the type checker can reason about.

§ 06

History

Haskell 1.0 in 1990 included deriving for a fixed set of classes, and the Haskell 98 report specifies the generated code[4]. GHC added generalized newtype deriving in the early 2000s; its interaction with type families was found to be unsound, which led to the work on generative type abstraction and to roles in GHC 7.8[6][3]. DeriveAnyClass arrived with GHC's generic programming support[5]. DerivingStrategies was added in GHC 8.2 in 2017 and DerivingVia in GHC 8.6 in 2018[1][2].

see also

referenced by

further reading

  1. [1]B. Blöndal, A. Löh, R. Scott, “Deriving Via: or, how to turn hand-written instances into an anti-pattern”, Haskell Symposium (2018).
  2. [2]GHC User’s Guide, “Deriving strategies”.
  3. [3]J. Breitner, R. A. Eisenberg, S. Peyton Jones, S. Weirich, “Safe zero-cost coercions for Haskell”, Journal of Functional Programming 26 (2016).
  4. [4]S. Peyton Jones (ed.), Haskell 98 Language and Libraries: The Revised Report, ch. 10, Cambridge University Press (2003).
  5. [5]J. P. Magalhães, A. Dijkstra, J. Jeuring, A. Löh, “A generic deriving mechanism for Haskell”, Haskell Symposium (2010).
  6. [6]S. Weirich, D. Vytiniotis, S. Peyton Jones, S. Zdancewic, “Generative type abstraction and type-level computation”, POPL (2011).

last updated