6.6.8. Deriving via

DerivingVia
Implies:DerivingStrategies
Since:8.6.1

This allows deriving a class instance for a type by specifying another type that is already an instance of that class. This only makes sense if the methods have identical runtime representations, in the sense that coerce (see The Coercible constraint) can convert the existing implementation into the desired implementation. The generated code will be rejected with a type error otherwise.

DerivingVia is indicated by the use of the via deriving strategy. via requires specifying another type (the via type) to coerce through. For example, this code:

{-# LANGUAGE DerivingVia #-}

import Numeric

newtype Hex a = Hex a

instance (Integral a, Show a) => Show (Hex a) where
  show (Hex a) = "0x" ++ showHex a ""

newtype Unicode = U Int
  deriving Show
    via (Hex Int)

-- >>> euroSign
-- 0x20ac
euroSign :: Unicode
euroSign = U 0x20ac

Generates the following instance

instance Show Unicode where
  show :: Unicode -> String
  show = Data.Coerce.coerce
    @(Hex Int -> String)
    @(Unicode -> String)
    show

This extension generalizes GeneralizedNewtypeDeriving. To derive Num Unicode with GND (deriving newtype Num) it must reuse the Num Int instance. With DerivingVia, we can explicitly specify the representation type Int:

newtype Unicode = U Int
  deriving Num
    via Int

  deriving Show
    via (Hex Int)

euroSign :: Unicode
euroSign = 0x20ac

Code duplication is common in instance declarations. A familiar pattern is lifting operations over an Applicative functor. Instead of having catch-all instances for f a which overlap with all other such instances, like so:

instance (Applicative f, Semigroup a) => Semigroup (f a) ..
instance (Applicative f, Monoid    a) => Monoid    (f a) ..

We can instead create a newtype App (where App f a and f a are represented the same in memory) and use DerivingVia to explicitly enable uses of this pattern:

{-# LANGUAGE DerivingVia, DeriveFunctor, GeneralizedNewtypeDeriving #-}

import Control.Applicative

newtype App f a = App (f a) deriving newtype (Functor, Applicative)

instance (Applicative f, Semigroup a) => Semigroup (App f a) where
  (<>) = liftA2 (<>)

instance (Applicative f, Monoid a) => Monoid (App f a) where
  mempty = pure mempty

data Pair a = MkPair a a
  deriving stock
    Functor

  deriving (Semigroup, Monoid)
    via (App Pair a)

instance Applicative Pair where
  pure a = MkPair a a

  MkPair f g <*> MkPair a b = MkPair (f a) (g b)

Note that the via type does not have to be a newtype. The only restriction is that it is coercible with the original data type. This means there can be arbitrary nesting of newtypes, as in the following example:

newtype Kleisli m a b = Kleisli (a -> m b)
  deriving (Semigroup, Monoid)
    via (a -> App m b)

Here we make use of the Monoid ((->) a) instance.

When used in combination with StandaloneDeriving we swap the order for the instance we base our derivation on and the instance we define e.g.:

deriving via (a -> App m b) instance Monoid (Kleisli m a b)