🎓 A1Learner
🔍
0 of 47 lessons completed

Haskell Generics Patterns

A generics pattern is a reusable shape for generic code. A round-trip helper, a piece-wise generic function, and deriving for each new type are the three shapes used here.

One helper checks any round trip

The helper takes any Generic value and rebuilds it. Box 7 rebuilds unchanged.

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics (Generic, from, to)
data Box a = Box a deriving (Show, Eq, Generic)
roundTrip :: (Generic a, Eq a) => a -> Bool
roundTrip x = to (from x) == x
main :: IO ()
main = print (roundTrip (Box 7))

The same helper checks a two-field type

No new helper code is needed for Pair. The same function checks it unchanged.

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics (Generic, from, to)
data Pair a b = Pair a b deriving (Show, Eq, Generic)
roundTrip :: (Generic a, Eq a) => a -> Bool
roundTrip x = to (from x) == x
main :: IO ()
main = print (roundTrip (Pair 3 "hi"))

A new type gains the count helper for free

Deriving Generic is the only step a new type needs. The count helper then works for it at once.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
import GHC.Generics
data Widget = Widget String Int deriving (Show, Eq, Generic)
class GCount f where
  gcount :: f p -> Int
instance GCount (K1 i c) where
  gcount _ = 1
instance GCount f => GCount (M1 i c f) where
  gcount (M1 x) = gcount x
instance (GCount f, GCount g) => GCount (f :*: g) where
  gcount (x :*: y) = gcount x + gcount y
instance GCount U1 where
  gcount _ = 0
genericCount :: (Generic a, GCount (Rep a)) => a -> Int
genericCount x = gcount (from x)
main :: IO ()
main = print (genericCount (Widget "pen" 3))

Example

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
import GHC.Generics
data Gadget = Gadget String Int deriving (Show, Eq, Generic)
roundTrip :: (Generic a, Eq a) => a -> Bool
roundTrip x = to (from x) == x
class GCount f where
  gcount :: f p -> Int
instance GCount (K1 i c) where
  gcount _ = 1
instance GCount f => GCount (M1 i c f) where
  gcount (M1 x) = gcount x
instance (GCount f, GCount g) => GCount (f :*: g) where
  gcount (x :*: y) = gcount x + gcount y
instance GCount U1 where
  gcount _ = 0
genericCount :: (Generic a, GCount (Rep a)) => a -> Int
genericCount x = gcount (from x)
main :: IO ()
main = do print (roundTrip (Gadget "pen" 3)); print (genericCount (Gadget "pen" 3))

Key points

  • Round-trip helper: one function that checks rebuilding for any Generic type.
  • Piece-wise function: a generic function written once per representation piece.
  • Deriving habit: every new type derives Generic so generic helpers work for it.
  • Reuse: the same helper works for a new type with no new code.
  • Equality check: the round-trip proof, which compares the rebuilt value with the original.

In this example, Gadget derives Generic, the round-trip helper rebuilds it unchanged for True, and the count helper reads its shape through from for 2.

Note

a generic helper needs an Eq instance when it compares rebuilt values.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Haskell Playground

Exercise

Why derive Generic for a new type?