Haskell is a purely functional language with a strong static type system. Functions have no side effects, data is immutable by default, and the type checker prevents entire categories of bugs at compile time. Today you learn the fundamentals.
A pure function always returns the same output for the same input and has no side effects. In Haskell, this is enforced by the type system — a function that does I/O must have the IO type in its signature, making side effects explicit and visible. Immutability means variables never change; instead, you create new values. This makes reasoning about code far easier and enables safe parallelism.
Haskell's type system is Hindley-Milner — it infers types without annotations in most cases. Types: Int, Integer (arbitrary precision), Double, Bool, Char, String ([Char]), Maybe a (optional value), Either e a (error or value), and [a] (list). Type classes define behavior: Eq (equality), Ord (ordering), Show (to string), Num (arithmetic), Functor, Monad. Types are checked at compile time; runtime type errors are essentially impossible.
Functions are defined with equations. Pattern matching deconstructs values: match on constructors, literals, or wildcards (_). Guards add conditions. Where clauses define local bindings. Let expressions define local bindings inside an expression. Function application is left-associative and highest precedence: 'f x y' means '(f x) y'. The ($) operator applies a function to an argument: 'f $ g x' means 'f (g x)'.
-- Type signatures are optional but idiomatic
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
-- Pattern matching on lists
listSum :: [Int] -> Int
listSum [] = 0
listSum (x:xs) = x + listSum xs
-- Guards
bmiCategory :: Double -> String
bmiCategory bmi
| bmi < 18.5 = "Underweight"
| bmi < 25.0 = "Normal"
| bmi < 30.0 = "Overweight"
| otherwise = "Obese"
-- Where clause
circleArea :: Double -> Double
circleArea r = pi * r2
where r2 = r * r
-- Let expression
cylinderVolume :: Double -> Double -> Double
cylinderVolume r h =
let area = pi * r * r
in area * h
Write a complete Caesar cipher in Haskell: encode :: Int -> String -> String and decode :: Int -> String -> String. Handle both upper and lower case letters; leave non-letters unchanged.