main :: IO ()

haskell.quest

A quest through the landscape of pure functional programming

λ

Definitions

Pure Functions

A function that, given the same input, will always return the same output and produces no side effects. The cornerstone of referential transparency.

double :: Int -> Int
double x = x * 2

Immutability

Values in Haskell cannot be modified after creation. There is no assignment, only binding. Data structures are persistent, sharing structure across versions.

let xs = [1,2,3]
let ys = 0 : xs -- [0,1,2,3]

Lazy Evaluation

Expressions are not evaluated until their values are needed. This enables infinite data structures and separates the description of computation from its execution.

take 5 [1..] -- [1,2,3,4,5]
-- The infinite list is never fully realized

Lemma

Maybe

The type of values that might not exist. A type-safe replacement for null that forces you to handle the absent case.

data Maybe a = Nothing | Just a

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

Either

A value that is one of two types. Conventionally, Left holds errors and Right holds successes. A richer alternative to Maybe when you need to know why something failed.

data Either a b = Left a | Right b

parseAge :: String -> Either String Int
parseAge s = case readMaybe s of
  Nothing -> Left "not a number"
  Just n -> Right n

Pattern Matching

Decompose data by its structure. Every algebraic data type can be taken apart as precisely as it was put together.

describeList :: [a] -> String
describeList xs = case xs of
  [] -> "empty"
  [_] -> "singleton"
  _ -> "longer list"

Theorem

Monad

The abstraction for sequencing computations that produce effects. Bind (>>=) takes a value in context and a function that produces a new context, composing them cleanly.

class Applicative m => Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  return :: a -> m a

Functor

A type that can be mapped over. The fmap function applies a function to the value inside a context without altering the context itself.

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Applicative

Between Functor and Monad. Apply functions that are themselves wrapped in context to values in context.

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

Do Notation

Syntactic sugar that makes monadic code read like imperative programming, while preserving all the guarantees of the type system.

main :: IO ()
main = do
  name <- getLine
  putStrLn ("Hello, " ++ name)

Type Classes

Ad-hoc polymorphism. A type class defines an interface; instances provide implementations for specific types. Constraints propagate through the type system.

class Eq a where
  (==) :: a -> a -> Bool

Higher-Kinded Types

Types that take type constructors as arguments. This enables abstractions over containers themselves, not just their contents.

-- f is a type constructor (* -> *)
class Functor (f :: * -> *) where ...

Q.E.D.

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

main :: IO ()
main = do
  let result = safeDivide 10 3
  case result of
    Nothing -> putStrLn "Cannot divide by zero"
    Just n -> putStrLn ("Result: " ++ show n)

The quest continues.