Haskell was first designed in 1987 by a committee of researchers in functional programming, primarily to serve as a standardized language for functional programming.
It was developed to overcome the limitations of earlier functional programming languages, emphasizing purity, laziness, and type safety.
Haskell was created as a pure functional programming language, meaning that functions are treated as mathematical functions with no side effects. This makes reasoning about programs easier and allows for more reliable and predictable code.
Haskell introduced innovations like lazy evaluation, type inference, and immutable data which influenced modern programming languages.
While initially not widely adopted in industry, Haskell has remained influential in academia and in certain specialized areas like compilers, financial systems, and research.
Who:
Haskell was named after the mathematician Haskell Curry, who made significant contributions to combinatory logic and functional programming theory.
The language was designed by a committee of researchers, including Simon Peyton Jones, John Hughes, Phil Wadler, and others.
The language and its ecosystem are maintained by the Haskell Committee, and the Haskell Foundation helps support the community and the development of related tools and libraries.
Why:
Haskell was created to provide a pure, declarative alternative to imperative programming languages like C and Java, offering better tools for building reliable, concurrent, and scalable systems.
Its focus on pure functions, lazy evaluation, and strong, static typing makes Haskell a great choice for systems where correctness, maintainability, and performance are critical.
It was also developed to explore and push forward the theoretical foundations of computer science, especially in the areas of category theory, lambda calculus, and logic.
Introduction
Advantages
Pure Functional Programming — Functions are pure, meaning they return the same result for the same arguments and have no side effects, making programs highly predictable and easy to reason about.
Lazy Evaluation — Calculations are deferred until their results are actually needed, enabling infinite data structures and modular program design.
Strong Static Typing & Inference — Types are checked at compile time, eliminating a massive class of runtime bugs. Haskell’s type inference engine automatically deduces types without requiring boilerplate annotations.
Concurrency & Parallelism — Lightweight thread models, Software Transactional Memory (STM), and pure computations make concurrent programming safe and free from data races.
Steep Learning Curve — Advanced concepts like Monads, Functors, Applicatives, Monad Transformers, and Category Theory can be daunting for developers used to imperative code.
Lazy Evaluation Overhead — Lazy evaluation can introduce memory leaks (space leaks) if thunks accumulate in memory without being evaluated.
Smaller Ecosystem — Fewer libraries and frameworks for rapid enterprise web development compared to languages like Java, Python, or Go.
Compilation Speeds — Large Haskell projects can have long compile times under GHC due to extensive type checking and optimization passes.
Remember Points
Pure by Default — All functions are pure unless explicitly wrapped in the IO monad.
Immutable Data — Variables are bound to values once; they cannot be mutated in place.
Lazy by Default — Evaluation is deferred until values are forced by an I/O action or pattern match.
Basics
Hello World & Entry Point
module Main wheremain :: IO ()main = putStrLn "Hello, World!"
module Main where declares the current module as the program entry point.
main :: IO () is the type signature of the entry point function. It performs I/O and returns a unit type ().
putStrLn prints a string to the console with a trailing newline.
Comments
-- This is a single-line comment.{- This is a multi-line comment block.-}-- | Documentation comment for functions (Haddock style)myFunc :: Int -> IntmyFunc x = x + 1
Variables and Local Bindings
-- Variables in Haskell are immutable bindsx :: Intx = 10-- Local bindings using 'let ... in' (Expression-level binding)calculateArea :: Double -> Double -> DoublecalculateArea width height = let area = width * height in area-- Local bindings using 'where' (Declaration-level binding)calculateVolume :: Double -> Double -> Double -> DoublecalculateVolume w h d = area * d where area = w * h
Primitive Data Types Table
Type Description
Int Fixed-precision signed integer (usually 64-bit)
Integer Arbitrary-precision signed integer (unbounded size)
Float Single-precision floating-point number
Double Double-precision floating-point number
Bool Boolean value (True or False)
Char Unicode character (written in single quotes, e.g., 'a')
String Alias for [Char] (List of characters)
() Unit type (contains exactly one value: ())
-- in Haskell, 'if' is an expression that must always return a value-- both the 'then' and 'else' branches are mandatory and must return the same typecheckAge :: Int -> StringcheckAge age = if age >= 18 then "Adult" else "Minor"
Case Expressions
-- Pattern match values directly on expressionsdescribeList :: [a] -> StringdescribeList xs = case xs of [] -> "empty list" [x] -> "singleton list" (x:y:_) -> "long list"
Guard Equations
-- Guards provide a clean way to test conditional properties on argumentsbmiTell :: Double -> StringbmiTell bmi | bmi <= 18.5 = "Underweight" | bmi <= 25.0 = "Normal" | bmi <= 30.0 = "Overweight" | otherwise = "Obese"
Recursion
-- Since Haskell has no loops, recursion is used for iterationfactorial :: Integer -> Integerfactorial 0 = 1 -- Base casefactorial n = n * factorial (n - 1) -- Recursive case-- Tail-recursive version with accumulator (helps compile to efficient loops)factorialTail :: Integer -> IntegerfactorialTail n = go n 1 where go 0 acc = acc go k acc = go (k - 1) (acc * k)
Functions
Type Signatures & Currying
-- Every function in Haskell takes exactly one argument and returns another function-- This is called Currying.add :: Int -> Int -> Intadd x y = x + y-- Partial ApplicationaddFive :: Int -> IntaddFive = add 5 -- returns a function expecting the second argument
Pattern Matching in Functions
-- Destructure patterns directly in parameter definitionsfirst :: (a, b, c) -> afirst (x, _, _) = xhead' :: [a] -> ahead' [] = error "Empty list!"head' (x:_) = x -- extract head and discard tail
Lambdas (Anonymous Functions)
-- Syntax: \argument -> expressionaddLambda = \x y -> x + ydoubleList = map (\x -> x * 2) [1, 2, 3] -- [2, 4, 6]
Higher-Order Functions
-- Functions that take functions as arguments or return functionsapplyTwice :: (a -> a) -> a -> aapplyTwice f x = f (f x)-- applyTwice (+3) 10 -> 16
Function Composition & Application
-- Function composition: (f . g) x = f (g x)-- Function application: f $ x = f xnegateAndSum :: [Int] -> IntnegateAndSum = negate . sum -- clean, point-free style (no arguments declared)-- $ avoids nested parenthesesval = putStrLn $ show $ sum [1..10]
Custom Data Types
Type Synonyms
-- Creates an alias for an existing type (no new type constructed)type PhoneNumber = Stringtype Name = Stringtype PhoneBook = [(Name, PhoneNumber)]
Algebraic Data Types (ADT)
-- 1. Sum Type (enumerations)data Direction = North | South | East | West-- 2. Product Type (records/structs)data Point = Point Double Double -- constructor and arguments-- 3. Sum of Products with Record Syntax (named fields)data Person = Person { name :: String, age :: Int} deriving (Show, Eq)-- Automatic getters generated:-- name :: Person -> String-- age :: Person -> Int
Newtype (Zero-overhead Type Wrapping)
-- Wraps exactly one type at compile-time for safety, erased at runtimenewtype CustomerId = CustomerId Int
Type Classes
Built-in Type Classes
-- Type classes define interfaces for types.-- Eq - Defines equality (==, /=)-- Ord - Defines ordering (<, >, <=, >=, compare)-- Show - Defines string formatting (show)-- Read - Defines parsing from string (read)-- Num - Mathematical operations (+, -, *, abs)-- Deriving classes automaticallydata Color = Red | Blue | Green deriving (Show, Eq, Ord)
Defining Custom Type Classes
class Printable a where printMe :: a -> String-- Implement Type Class Instanceinstance Printable Int where printMe x = "Integer: " ++ show xinstance Printable String where printMe s = "String: " ++ s
Functors, Applicatives & Monads
Functor
-- A Functor represents context that can be mapped over.-- fmap :: (a -> b) -> f a -> f b-- Infix operator: <$>x :: Maybe Intx = (+3) <$> Just 5 -- Just 8y :: Maybe Inty = (+3) <$> Nothing -- Nothing
Applicative
-- Applicatives allow applying functions wrapped in contexts to values wrapped in contexts.-- pure :: a -> f a-- (<*>) :: f (a -> b) -> f a -> f bres = Just (+) <*> Just 5 <*> Just 3 -- Just 8
Monads & Do-Notation
-- Monads sequence operations, handling side effects (errors, I/O, state).-- (>>=) :: m a -> (a -> m b) -> m b -- Bind Operator-- return :: a -> m a-- Standard bind syntaxhalf :: Int -> Maybe Inthalf x = if even x then Just (x `div` 2) else Nothingval = Just 20 >>= half >>= half -- Just 5-- Do-notation (Syntactic sugar for Monad bind operations)getUserInfo :: Maybe (String, Int)getUserInfo = do name <- Just "Alice" age <- Just 30 return (name, age)
Lazy Evaluation
Thunks & Lazy Semantics
Haskell stores expressions in memory as thunks (unevaluated expressions wrapped in pointers).
An expression is only evaluated to “Weak Head Normal Form” (WHNF) when forced by I/O operations or pattern matching.
-- Infinite list definition (safe under lazy evaluation)infiniteOnes = 1 : infiniteOnes-- Take first 5 elements (evaluates only what is needed)firstFive = take 5 infiniteOnes -- [1, 1, 1, 1, 1]
Strictness Control
To avoid memory leaks (space leaks caused by thunk accumulation), you can force eager evaluation:
-- 1. seq function (forces evaluation of first arg before returning second)sumStrict :: Int -> [Int] -> IntsumStrict acc [] = accsumStrict acc (x:xs) = let nextAcc = acc + x in nextAcc `seq` sumStrict nextAcc xs-- 2. strict application operator ($!)result = f $! x -- forces x before applying f-- 3. Bang Patterns (Requires GHC Extension BangPatterns)sumBang :: Int -> [Int] -> IntsumBang !acc [] = acc -- forces evaluation of accsumBang !acc (x:xs) = sumBang (acc + x) xs
Exception Handling
Pure Exceptions
-- throwing pure errors (dangerous, causes crash on evaluation)divide :: Int -> Int -> Intdivide _ 0 = error "Division by zero!"divide x y = x `div` y
Impure Exceptions (IO Monad)
import Control.ExceptionreadAndHandle :: IO ()readAndHandle = do result <- try (readFile "non_existent.txt") :: IO (Either IOException String) case result of Left ex -> putStrLn $ "Caught file error: " ++ show ex Right contents -> putStrLn contents
Concurrency & Parallelism
Green Threads & MVars
import Control.Concurrentmain :: IO ()main = do mvar <- newEmptyMVar -- Synced communication variable (like a blocking box) -- Fork lightweight thread forkIO $ do threadDelay 1000000 -- microseconds (1s) putMVar mvar "Worker finished" print "Waiting for worker..." val <- takeMVar mvar -- Blocks until variable populated print val
Software Transactional Memory (STM)
import Control.Concurrent.STM-- STM guarantees atomic transaction blocks, preventing deadlocks & race conditionstransfer :: TVar Int -> TVar Int -> Int -> STM ()transfer fromAcc toAcc amount = do fromBal <- readTVar fromAcc if fromBal < amount then retry -- Aborts transaction and waits for TVar change to retry else do writeTVar fromAcc (fromBal - amount) toBal <- readTVar toAcc writeTVar toAcc (toBal + amount)executeTransfer :: TVar Int -> TVar Int -> Int -> IO ()executeTransfer from to amt = atomically $ transfer from to amt
File I/O
Lazy I/O vs Strict I/O
import System.IOimport qualified Data.Text.IO as TIOmain :: IO ()main = do -- Standard lazy write/read writeFile "output.txt" "First line\nSecond line\n" contents <- readFile "output.txt" -- file handles stay open lazily until string evaluated putStr contents -- Strict Text I/O (recommended to avoid file lock leaks) strictText <- TIO.readFile "output.txt" TIO.putStr strictText
import Data.List -- Import all functionsimport Data.Map (lookup, insert) -- Selective importimport Data.Set hiding (null) -- Hide specific name collisionsimport qualified Data.Text as T -- Namespace alias
Advanced Haskell
GHC Extensions
Haskell compilers support language extensions declared at the top of the file:
{-# LANGUAGE OverloadedStrings #-} -- Allows String literals to match Text/ByteString types{-# LANGUAGE BangPatterns #-} -- Allows bang (!) patterns for strictness{-# LANGUAGE MultiParamTypeClasses #-} -- Type classes with multiple parameters{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- Automatic deriving of underlying types for newtypes
Foreign Function Interface (FFI)
{-# LANGUAGE ForeignFunctionInterface #-}module Main whereimport Foreign.C.Types-- Call external C functionforeign import ccall "sin" c_sin :: CDouble -> CDoublemain :: IO ()main = print $ c_sin 1.0