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.userNameUpdates 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 ++ " " ++ userNameMaybe 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.