Skip to content

Haskell Custom Types Guide — Algebraic Data Types, Newtypes, and GADTs

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Haskell Custom Types Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Haskell custom types use data declarations with sum types (alternatives via |) and product types (multiple fields per constructor) -- with newtype for compile-time-only wrappers and GADTs for constructor-specific type constraints.

Algebraic Data Types

-- Sum type (OR): one of several alternatives
data Bool = False | True
data Direction = North | South | East | West

-- Product type (AND): multiple fields together
data Point = Point Float Float
data Person = Person String Int

-- Mixed: sum of products
data Shape
    = Circle Float          -- radius
    | Rectangle Float Float -- width, height
    deriving (Show)

-- Usage
area :: Shape -> Float
area (Circle r)       = pi * r ^ 2
area (Rectangle w h)  = w * h

Newtype

-- Zero-cost wrapper
newtype Age = Age Int
newtype Name = Name String

-- Type safety without runtime cost
mkAge :: Int -> Maybe Age
mkAge n
    | n >= 0 && n <= 150 = Just (Age n)
    | otherwise           = Nothing

-- Newtype deriving
newtype Meter = Meter Double
    deriving (Num, Show, Eq, Ord)

distance :: Meter -> Meter -> Meter
distance x y = abs (x - y)

Recursive Types

-- Recursive data type
data Nat = Zero | Succ Nat

natToInt :: Nat -> Int
natToInt Zero     = 0
natToInt (Succ n) = 1 + natToInt n

-- Peano arithmetic example
add :: Nat -> Nat -> Nat
add Zero     n = n
add (Succ m) n = Succ (add m n)

-- Binary tree
data Tree a
    = Leaf
    | Node (Tree a) a (Tree a)
    deriving (Show)

GADTs (Generalized Algebraic Data Types)

{-# LANGUAGE GADTs #-}

-- Type-safe expressions
data Expr a where
    IntLit   :: Int -> Expr Int
    BoolLit  :: Bool -> Expr Bool
    Add      :: Expr Int -> Expr Int -> Expr Int
    Not      :: Expr Bool -> Expr Bool
    IfElse   :: Expr Bool -> Expr a -> Expr a -> Expr a

eval :: Expr a -> a
eval (IntLit n)       = n
eval (BoolLit b)      = b
eval (Add x y)        = eval x + eval y
eval (Not x)          = not (eval x)
eval (IfElse c t e)   = if eval c then eval t else eval e

Common Mistakes

1. Infinite types

A type that contains itself directly: data Bad = Bad [Bad] is OK (indirect), but data Bad = Bad Bad is infinite.

2. Overusing newtype vs data

Use newtype for single-constructor single-field wrappers (zero cost). Use data for multiple fields or constructors.

3. Pattern matching incompleteness

Exhaustive pattern matching requires covering all constructors. Use -Wall to get warnings about incomplete patterns.

Practice Questions

1. What is the difference between data and newtype? newtype has exactly one constructor with one field and zero runtime cost. data can have multiple constructors/fields.

2. What is a sum type? A type with multiple constructors (alternatives): data Bool = True | False. It represents "one of these options."

3. What is a product type? A type with multiple fields per constructor: data Point = Point Float Float. It represents "all of these fields."

FAQ

{{< faq question="What is the difference between algebraic and OOP types?" >}} Algebraic types are closed (all variants known at compile time). OOP classes are open (anyone can add subclasses). {{< /faq >}}

{{< faq question="Can I use record syntax with GADTs?" >}} Yes. GADT constructors can use record syntax: data Expr a where { IntLit { val :: Int } :: Expr Int }. {{< /faq >}}

{{< faq question="What is a phantom type?" >}} A type parameter that doesn't appear in the constructor's fields: data Formatted a = Formatted String. The a is a phantom used only for type safety. {{< /faq >}}

What's Next

Now learn about Haskell modules.

Topic Description Link
Modules Module system and imports {{< ref "16-modules" >}}
Lazy Evaluation Laziness and thunks {{< ref "17-lazy-evaluation" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro