Skip to content

Haskell GADTs Guide — Generalized Algebraic Data Types and Type Safety

DodaTech Updated 2026-06-28 3 min read

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