Haskell GADTs Guide — Generalized Algebraic Data Types and Type Safety
In this tutorial, you will learn about Haskell GADTs Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Haskell GADTs extend regular algebraic data types by letting each constructor specify its return type explicitly -- enabling phantom type parameters, type-safe embedded DSLs, and compile-time elimination of invalid states through precise type annotations per constructor.
Basic GADT
{-# LANGUAGE GADTs #-}
-- Regular ADT
data Expr = IntLit Int | BoolLit Bool | Add Expr Expr
-- GADT version
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
If :: Expr Bool -> Expr a -> Expr a -> Expr a
Type-Safe Evaluation
eval :: Expr a -> a
eval (IntLit n) = n
eval (BoolLit b) = b
eval (Add x y) = eval x + eval y
eval (If c t e) = if eval c then eval t else eval e
-- Type safety: this won't compile
-- eval (Add (BoolLit True) (IntLit 5)) -- Type error!
-- Usage
example = eval (Add (IntLit 2) (Add (IntLit 3) (IntLit 4)))
-- 9
Safe Vector
{-# LANGUAGE GADTs, DataKinds #-}
data Nat = Zero | Succ Nat
data Vec n a where
VNil :: Vec Zero a
VCons :: a -> Vec n a -> Vec (Succ n) a
-- Type-safe head
vhead :: Vec (Succ n) a -> a
vhead (VCons x _) = x
-- Type-safe append
vappend :: Vec n a -> Vec m a -> Vec (Succ n + m) a
vappend VNil ys = ys
vappend (VCons x xs) ys = VCons x (vappend xs ys)
Existential Types
{-# LANGUAGE GADTs #-}
data Showable where
MkShowable :: Show a => a -> Showable
showAll :: [Showable] -> String
showAll xs = unlines [show s | MkShowable s <- xs]
-- Usage
items = [MkShowable 42, MkShowable "hello", MkShowable True]
Common Mistakes
1. Forgetting extensions
GADTs require {-# LANGUAGE GADTs #-} at the top of the file without it, the syntax is rejected.
2. Pattern matching exhaustiveness
GHC can't always check exhaustiveness for GADTs. Use -Wincomplete-patterns.
3. Overusing GADTs
Regular ADTs are simpler. Use GADTs only when you need constructor-specific type constraints or phantom types.
Practice Questions
1. What is the difference between a regular ADT and a GADT? GADT constructors specify their return type explicitly, allowing different constructors to have different type parameters.
2. How does eval guarantee type safety? Each constructor's type constrains what expressions can be combined. Add only accepts Expr Int, not Expr Bool.
3. What is an existential GADT?
A GADT that hides a type variable: data Showable where MkShowable :: Show a => a -> Showable.
FAQ
{{< faq question="Can I use record syntax with GADTs?" >}}
Yes. data Expr a where { IntLit { val :: Int } :: Expr Int } creates a named field accessor.
{{< /faq >}}
{{< faq question="Do GADTs have performance overhead?" >}} Minimal. GADTs are a compile-time feature. The runtime representation is the same as regular ADTs. {{< /faq >}}
{{< faq question="What is the relationship between GADTs and existentials?" >}} GADTs naturally support existential types when a type variable appears only in the constructor, not in the result type. {{< /faq >}}
What's Next
Now learn about type families.
| Topic | Description | Link |
|---|---|---|
| Type Families | Type-level functions | {{< ref "29-type-families" >}} |
| Template Haskell | Meta-programming | {{< ref "30-template-haskell" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro