Monads are Haskell's mechanism for sequencing computations with effects. The IO monad wraps all real-world interactions. Maybe and Either model computations that can fail. Today you demystify monads and write practical I/O programs.
Maybe a is either Just a (success) or Nothing (failure). Chaining Maybe computations with >>= (bind) short-circuits on Nothing: 'lookup key m >>= lookup key2' — if the first lookup returns Nothing, the second never runs. Do-notation desugars to bind: 'do { x <- mx; y <- my; return (x+y) }' becomes 'mx >>= \x -> my >>= \y -> return (x+y)'. This eliminates null-check pyramids.
IO a represents a computation that performs I/O and produces a value of type a. IO actions are values — combining them with >>= sequences their execution. 'main :: IO ()' is the entry point. Common actions: putStrLn, getLine, readFile, writeFile, hSetBuffering. IO keeps pure functions separate from side effects: a function that returns IO Double announces it has side effects; a function returning Double is guaranteed pure.
Either e a represents Right a (success) or Left e (error with value of type e). Use it instead of exceptions for expected failures. 'parseAge :: String -> Either String Int' returns Left 'not a number' or Right 42. The Either monad short-circuits on Left, just like Maybe on Nothing. 'ExceptT' from transformers stacks Either with IO for programs with both effects.
import System.IO
import Data.Maybe (mapMaybe)
import Text.Read (readMaybe)
-- Safe integer parsing
safeParseInt :: String -> Maybe Int
safeParseInt = readMaybe
-- Chain Maybe with do-notation
lookupAndDouble :: String -> [(String, Int)] -> Maybe Int
lookupAndDouble key db = do
val <- lookup key db
double <- safeParseInt (show (val * 2))
return double
-- IO: interactive sum calculator
main :: IO ()
main = do
hSetBuffering stdout LineBuffering
putStrLn "Enter numbers (blank to finish):"
nums <- collectNums
putStrLn $ "Sum: " ++ show (sum nums)
putStrLn $ "Count: " ++ show (length nums)
collectNums :: IO [Int]
collectNums = do
line <- getLine
if null line
then return []
else case readMaybe line :: Maybe Int of
Nothing -> do putStrLn "Not a number, skipping."
collectNums
Just n -> do rest <- collectNums
return (n : rest)
Write a CSV parser in Haskell that handles quoted fields with embedded commas. Return Either String [[String]] — Left for parse errors, Right for a 2D grid of fields. Test it on a 10,000-row CSV file.