Cutting Boilerplate in Haskell with DerivingVia: A Practical Guide
Learn how Haskell’s DerivingVia extension cuts boilerplate by reusing existing instances for newtypes. A step‑by‑step guide with code examples, trade‑offs, and a quick checklist to get you started.
18 Dec 2025, 22:04 UTC

Problem: Repetitive Typeclass Boilerplate
When you add a new data type to a Haskell project, you often need to write or derive instances for common typeclasses such as Show, Eq, Ord, or even custom ones. Even with GHC’s deriving syntax, you still have to repeat the boilerplate for each newtype or record. This repetition grows quickly in large codebases and can become a maintenance headache when the underlying representation changes.
Thesis: Use DerivingVia to Reuse Existing Instances
GHC’s DerivingVia extension lets you delegate the creation of an instance to another type that already has a suitable instance. By wrapping your new type in a newtype and specifying a wrapper, you can inherit the behaviour of an existing instance without writing the code again. This keeps the code DRY, improves readability, and makes future refactors easier.
How DerivingVia Works
Basic Syntax
Enable the extension in your .cabal file or at the top of the module:
{-# LANGUAGE DerivingVia #-}
Define a newtype and use deriving via to point to the wrapper type that already has the desired instance:
newtype Email = Email { unEmail :: Text }
deriving (Eq, Show) via Newtype Text
Here Newtype is a standard wrapper that simply forwards the instance from Text. GHC rewrites the Show instance for Email to call show on the underlying Text.
Custom Wrapper for Complex Logic
Suppose you want Show to output a masked email address. You can write a small wrapper once and reuse it:
newtype MaskedShow a = MaskedShow a
instance Show a => Show (MaskedShow a) where
show (MaskedShow a) = "***" ++ show a
newtype Email = Email { unEmail :: Text }
deriving (Show) via MaskedShow Text
Now every newtype that derives Show via MaskedShow gets the same masking behaviour without duplicating the logic.
Concrete Example: Deriving a Custom Eq Instance
Imagine you have a type that stores a list of integers but you want equality to ignore ordering. Define a wrapper that implements Eq for sorted lists:
newtype SortedList a = SortedList [a]
instance Ord a => Eq (SortedList a) where
SortedList xs == SortedList ys = sort xs == sort ys
newtype UnorderedIds = UnorderedIds { getIds :: [Int] }
deriving (Eq) via SortedList Int
Now UnorderedIds compares equal regardless of list order, and you only wrote the sorting logic once.
Trade‑Offs and Limitations
- Readability for New Readers: The derived instance is not obvious; a reader must follow the
deriving viachain to understand the behaviour. - Compiler Support: Requires GHC 8.10 or newer. Projects that need to support older compilers cannot use this feature.
- Ambiguity: If multiple overlapping instances are available,
DerivingViacan cause ambiguity errors. The compiler will report these; you may need to add type annotations or remove conflicting instances. - Debugging: GHC’s error messages might refer to the wrapper type, which can be confusing when troubleshooting.
Actionable Checklist
- Enable
DerivingViain your project:{-# LANGUAGE DerivingVia #-} - Identify a wrapper that already implements the desired typeclass for the underlying type.
- Write a
newtypefor your data and derive via the wrapper.newtype MyType = MyType { unMyType :: SomeType } deriving (Show) via Wrapper SomeType - Compile with GHC 9.x and verify the instance behaves as expected (e.g., run
ghciand print a value). - Document the wrapper’s behaviour so future maintainers understand the derived instance.
Conclusion
DerivingVia is a powerful tool for reducing boilerplate and centralising instance logic in Haskell. By delegating instance creation to reusable wrappers, you keep your codebase concise and easier to maintain. Just be mindful of readability and compiler version constraints, and use clear documentation to keep the intent visible to anyone who reads the code later.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.