Algebraic Data Types (ADTs) are composite types in Haskell that allow you to create custom data structures.

See also: Pattern Matching and Recursion, Lists and List Comprehensions, Polymorphism, Type Synonyms, Newtype, Maybe, Either, Functors and Applicatives

Basic Syntax

ADTs are defined using the data keyword:

data TypeName = Constructor1 [Types] | Constructor2 [Types] | ...

Types of ADTs

Sum Types (Enumerations)

Sum types provide alternatives, similar to enums in other languages:

data Season = Spring | Summer | Autumn | Winter

Here, Season can be any one of the four constructors.

Product Types

Product types combine multiple values:

data Point = Point Float Float
--             ^      ^     ^
--             |      |     Second coordinate (Float)
--             |      First coordinate (Float)  
--             Constructor name

Mixed Sum and Product Types

Many ADTs combine both concepts:

data Shape = Circle Float  -- Radius
           | Rectangle Float Float  -- Width and Height
           | Triangle Float Float Float  -- Three sides

Record Syntax

For product types with many fields, record syntax provides named fields:

data Person = Person { name :: String
                     , age :: Int
                     , email :: String
                     }

Benefits of record syntax:

  • Automatic accessor functions (name, age, email)
  • Easier to create/update values with many fields
  • Better readability for complex types

Recursive Data Types

See: Lists and List Comprehensions, Pattern Matching and Recursion

ADTs can be recursive, referring to themselves in their definition:

data List a = Empty | Cons a (List a)
data Tree a = Leaf | Node a (Tree a) (Tree a)

These recursive structures enable complex data structures like lists, trees, and graphs.

Parameterized Types

See: Polymorphism, Maybe, Either

Types can be parameterized with type variables:

data Maybe a = Nothing | Just a
data Either a b = Left a | Right b

This enables generic programming, similar to generics in other languages.

Type Synonyms

Type synonyms create aliases for existing types:

type String = [Char]
type Name = String
type Age = Int
type Person = (Name, Age)

Unlike data, type doesn’t create a new type; it just provides an alternative name.

Newtype

For single-constructor, single-field types, newtype provides a more efficient alternative to data:

newtype Age = Age Int

Benefits of newtype:

  • No runtime overhead
  • Creates a distinct type (unlike type)
  • Compiler guarantees it has exactly one constructor with one field

Pattern Matching with ADTs

See: Pattern Matching and Recursion

ADTs are typically used with pattern matching:

showSeason :: Season -> String
showSeason Spring = "It's spring!"
showSeason Summer = "It's summer!"
showSeason Autumn = "It's autumn!"
showSeason Winter = "It's winter!"
area :: Shape -> Float
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle a b c) = sqrt (s * (s - a) * (s - b) * (s - c))
  where s = (a + b + c) / 2  -- Semi-perimeter

The Maybe Type

See: Error Handling, Common Monads

Maybe is a built-in ADT that represents optional values:

data Maybe a = Nothing | Just a

It’s used to handle potential failures without exceptions:

safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)

Deriving Typeclasses

See: Polymorphism, Functors and Applicatives

Common behaviors can be automatically derived:

data Season = Spring | Summer | Autumn | Winter
  deriving (Show, Eq, Ord, Enum)

This gives the type:

  • String representation (Show)
  • Equality testing (Eq)
  • Comparison operations (Ord)
  • Enumeration capabilities (Enum)

Key Points to Remember

  1. ADTs are the primary way to create custom data types in Haskell
  2. They can represent both alternatives (sum types) and combinations (product types)
  3. Pattern matching is the primary mechanism for working with ADTs
  4. Type parameters and recursion make ADTs extremely flexible
  5. Maybe and Either are important built-in ADTs for handling potential failures