Skip to content
Records, Show, and Read
step 1/7

Reading — step 1 of 7

Learn

~3 min readPractical Haskell

Show and Read are the type classes for converting between values and strings. LYAH covers them as foundational; every serious Haskell type implements at least Show.

Show — converting to string

class Show a where
    show :: a -> String

Derivable for most types:

data Point = Point Int Int deriving (Show)

show (Point 3 4)      -- "Point 3 4"
print (Point 3 4)      -- prints: Point 3 4

The print function is just putStrLn . show.

Custom Show — pretty printing

data Money = Money Int     -- cents

instance Show Money where
    show (Money c) =
        let (dollars, cents) = c `divMod` 100
        in '$' : show dollars ++ "." ++ pad (show cents)
      where
        pad s = replicate (2 - length s) '0' ++ s

show (Money 12345)     -- "$123.45"

Writing your own Show instance gives you full control over output. Useful for value objects (Money, Date, etc.).

Read — parsing from string

class Read a where
    readsPrec :: Int -> String -> [(a, String)]

Usage via the helper functions:

read "42" :: Int           -- 42
read "3.14" :: Double      -- 3.14
read "[1, 2, 3]" :: [Int]  -- [1, 2, 3]

-- Read THROWS on invalid input:
read "abc" :: Int          -- *** Exception: Prelude.read: no parse

Safer parsing — readMaybe and readEither

import Text.Read (readMaybe, readEither)

readMaybe "42" :: Maybe Int       -- Just 42
readMaybe "abc" :: Maybe Int      -- Nothing

readEither "42" :: Either String Int     -- Right 42
readEither "abc" :: Either String Int    -- Left "Prelude.read: no parse"

Use readMaybe or readEither, not bare read. The exception from read on invalid input is hard to catch and ugly to debug.

Show / Read round-trip

The convention: read . show should be identity for derived instances:

data Color = Red | Green | Blue deriving (Show, Read)

show Red                   -- "Red"
read "Red" :: Color        -- Red
read "Green" :: Color      -- Green
read "Yellow" :: Color     -- *** Exception: no parse

Derive Read alongside Show for serialization to/from text. Custom Show + custom Read needs careful thought to keep this round-trip property.

Show vs Display vs ToJSON

Show is for DEBUGGING. Real applications use:

  • Display (from text-display or RIO) — user-facing output
  • ToJSON/FromJSON (from aeson) — JSON serialization
  • Custom formatters for logs, CSV, etc.

Don't make Show output user-facing — it's meant for ghci and debugging.

Show and lists / tuples

show [1, 2, 3]            -- "[1,2,3]"
show (1, "foo", True)     -- "(1,\"foo\",True)"
show (Just 5)             -- "Just 5"
show Nothing              -- "Nothing"

Standard library types all have sensible Show instances. Lists, tuples, Maybe, Either — all printable out of the box.

When to derive vs hand-write

Derive Show for:

  • Debugging
  • Test output
  • Data types you control

Hand-write Show when:

  • Output should look different from the constructor (Money $123.45 not Money 12345)
  • Internal structure should be hidden
  • Round-trip with Read isn't needed

Common mistakes

  • read instead of readMaybe — invalid input throws an exception. Always prefer the safe variants.
  • Custom Show that breaks round-trip with Read — if you want round-trip, derive both. Mix carefully.
  • Show for user output — Show is for developers. Use a separate formatting function for users.
  • Read for non-trivial parsing — Read works for simple types; for complex parsing use a parser library (megaparsec, parsec).
  • Forgetting type annotation on readread "42" is ambiguous; need read "42" :: Int.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…