Haskell Cheatsheet

Data and Records

Use this Haskell reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Algebraic Data Types

data Direction = North | South | East | West
  deriving (Eq, Show)

turnAround :: Direction -> Direction
turnAround North = South
turnAround South = North
turnAround East = West
turnAround West = East

Constructors are values. Deriving adds standard typeclass instances.

Product Types

data Point = Point Double Double
  deriving (Eq, Show)

distanceFromOrigin :: Point -> Double
distanceFromOrigin (Point x y) = sqrt (x * x + y * y)

data Shape = Circle Double | Rect Double Double
  deriving (Show)

area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rect w h) = w * h

Sum types (|) and positional fields combine freely. GHC2021 also lets you derive Functor, Foldable, and Traversable for container-like types: data Pair a = Pair a a deriving (Show, Functor).

Records

data User = User
  { userId :: Int
  , userName :: String
  , userEmail :: String
  } deriving (Eq, Show)

ada = User { userId = 1, userName = "Ada", userEmail = "ada@example.com" }
renamed = ada { userName = "Ada Lovelace" }  -- update syntax, returns a copy

Record fields generate classic accessor functions like userName ada.

Record Dot Syntax

Modern codebases use OverloadedRecordDot (GHC 9.2+) for field access:

{-# LANGUAGE OverloadedRecordDot #-}

label :: User -> String
label u = show u.userId ++ ": " ++ u.userName

Updates still use record-update syntax (u { userName = ... }). For terse pattern matching, NamedFieldPuns (on in GHC2021) and RecordWildCards (opt-in) bind fields by name:

{-# LANGUAGE RecordWildCards #-}

render :: User -> String
render User{userName, userEmail} = userName ++ " <" ++ userEmail ++ ">"
renderAll User{..} = show userId ++ " " ++ userName

Maybe and Either

import Text.Read (readMaybe)

safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)

parseAge :: String -> Either String Int
parseAge s =
  case readMaybe s of
    Nothing -> Left ("not a number: " ++ s)
    Just n
      | n < 0     -> Left "age cannot be negative"
      | otherwise -> Right n

Use Maybe for absence and Either for success/failure with an error value. Never parse with bare read, it crashes on invalid input where readMaybe returns Nothing.

Newtype

newtype Email = Email String
  deriving (Eq, Show)

newtype Count = Count Int
  deriving newtype (Eq, Ord, Num)  -- reuse Int's instances directly

newtype creates a distinct type with one field and no runtime wrapper overhead, so Email and String can no longer be mixed up. The deriving newtype strategy needs DerivingStrategies (on by default in GHC2024). deriving (Generic) (via DeriveGeneric, on in GHC2021) is the hook most JSON libraries like aeson use to auto-derive encoders.