NexusCS

Haskell

Functional programming
Quick reference for Haskell - a purely functional programming language with strong static typing, lazy evaluation, and powerful type inference.
functional
lazy
pure
type-system

Getting started

Introduction

Haskell is a purely functional programming language with strong static typing, lazy evaluation, and powerful type inference.

Installation

# GHCup (recommended)
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh

# Verify installation
ghc --version
ghci  # Start REPL

Hello World

-- hello.hs
main :: IO ()
main = putStrLn "Hello, World!"
# Compile and run
ghc hello.hs
./hello

# Or run directly in GHCi
ghci hello.hs
> main

Quick Example

-- Function definition
square :: Int -> Int
square x = x * x

-- List operations
numbers = [1, 2, 3, 4, 5]
doubled = map (*2) numbers
-- [2, 4, 6, 8, 10]

-- Pattern matching
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)

Basic Syntax

Function Definition

-- Type signature (optional but recommended)
add :: Int -> Int -> Int
add x y = x + y

-- Infix notation
3 `add` 4  -- 7

-- Operator definition
(+++) :: String -> String -> String
x +++ y = x ++ " " ++ y

-- Guards
absolute :: Int -> Int
absolute n
  | n < 0     = -n
  | otherwise = n

-- Where clause
bmiTell :: Double -> Double -> String
bmiTell weight height
  | bmi <= 18.5 = "Underweight"
  | bmi <= 25.0 = "Normal"
  | otherwise   = "Overweight"
  where bmi = weight / height ^ 2

Let Expressions

-- Let in expression
cylinder :: Double -> Double -> Double
cylinder r h =
  let sideArea = 2 * pi * r * h
      topArea = pi * r ^ 2
  in  sideArea + 2 * topArea

-- Multiple bindings
let x = 5; y = 10 in x + y  -- 15

-- Pattern matching in let
let (a, b) = (1, 2) in a + b  -- 3

Comments

-- Single line comment

{- Multi-line
   comment -}

{-# LANGUAGE OverloadedStrings #-}  -- Language pragma

Basic Types

-- Primitive types
x :: Int        -- Fixed precision integer
y :: Integer    -- Arbitrary precision integer
z :: Float      -- Single precision float
w :: Double     -- Double precision float
b :: Bool       -- True or False
c :: Char       -- Unicode character
s :: String     -- String is [Char]

-- Type inference
value = 42      -- Inferred as Num a => a
text = "hello"  -- Inferred as String

Pattern Matching

Lists

-- Basic patterns
head' :: [a] -> a
head' []     = error "Empty list"
head' (x:_)  = x

-- Multiple elements
sumFirst3 :: [Int] -> Int
sumFirst3 (a:b:c:_) = a + b + c
sumFirst3 _         = 0

-- As-patterns
firstLetter :: String -> String
firstLetter all@(x:xs) = "First letter of " ++ all ++ " is " ++ [x]

-- Wildcards
length' :: [a] -> Int
length' []     = 0
length' (_:xs) = 1 + length' xs

Tuples

-- Pair patterns
fst' :: (a, b) -> a
fst' (x, _) = x

snd' :: (a, b) -> b
snd' (_, y) = y

-- Triple patterns
addTriple :: (Int, Int, Int) -> Int
addTriple (x, y, z) = x + y + z

Case Expressions

-- Case statement
describe :: [a] -> String
describe xs = case xs of
  []  -> "Empty list"
  [x] -> "Singleton list"
  _   -> "Longer list"

-- Pattern matching in case
processResult :: Either String Int -> String
processResult result = case result of
  Left err  -> "Error: " ++ err
  Right val -> "Success: " ++ show val

Record Syntax

-- Data type with records
data Person = Person
  { firstName :: String
  , lastName  :: String
  , age       :: Int
  }

-- Pattern matching records
getName :: Person -> String
getName Person{firstName = f, lastName = l} = f ++ " " ++ l

-- Shorthand
getAge :: Person -> Int
getAge Person{age = a} = a

Data Types

Type Aliases

-- Simple alias
type Name = String
type Age = Int
type PhoneNumber = String

-- Parameterized alias
type AssocList k v = [(k, v)]
type IntMap = AssocList Int

Algebraic Data Types

-- Sum types (OR)
data Bool = False | True

data Shape
  = Circle Float
  | Rectangle Float Float
  | Triangle Float Float Float

-- Using sum types
area :: Shape -> Float
area (Circle r) = pi * r ^ 2
area (Rectangle w h) = w * h
area (Triangle a b c) =
  let s = (a + b + c) / 2
  in sqrt (s * (s - a) * (s - b) * (s - c))

Recursive Types

-- List definition
data List a = Empty | Cons a (List a)

-- Binary tree
data Tree a
  = Leaf
  | Node a (Tree a) (Tree a)

-- Example tree
exampleTree :: Tree Int
exampleTree = Node 5
  (Node 3 Leaf Leaf)
  (Node 7 Leaf Leaf)

Newtypes

-- Single-constructor wrapper
newtype Email = Email String
newtype UserId = UserId Int

-- Unwrapping
getEmail :: Email -> String
getEmail (Email e) = e

-- Type safety
userId :: UserId
userId = UserId 123
-- userId = 123  -- Type error!

Type Classes

Common Type Classes

-- Eq: Equality comparison
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

-- Ord: Ordering
class Eq a => Ord a where
  compare :: a -> a -> Ordering
  (<), (<=), (>), (>=) :: a -> a -> Bool

-- Show: String representation
class Show a where
  show :: a -> String

-- Read: Parse from string
class Read a where
  read :: String -> a

-- Enum: Sequential types
class Enum a where
  succ, pred :: a -> a
  toEnum :: Int -> a
  fromEnum :: a -> Int

-- Num: Numeric types
class Num a where
  (+), (-), (*) :: a -> a -> a
  negate, abs, signum :: a -> a

Defining Instances

data TrafficLight = Red | Yellow | Green

-- Manual instance
instance Eq TrafficLight where
  Red    == Red    = True
  Yellow == Yellow = True
  Green  == Green  = True
  _      == _      = False

-- Derived instances
data Color = RGB Int Int Int
  deriving (Eq, Ord, Show, Read)

-- Instance with constraints
instance (Eq a) => Eq (Maybe a) where
  Nothing == Nothing = True
  Just x  == Just y  = x == y
  _       == _       = False

Functor

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

-- List instance
instance Functor [] where
  fmap = map

-- Maybe instance
instance Functor Maybe where
  fmap f (Just x) = Just (f x)
  fmap f Nothing  = Nothing

-- Usage
fmap (+1) [1, 2, 3]      -- [2, 3, 4]
fmap (*2) (Just 5)       -- Just 10
fmap show Nothing        -- Nothing

-- Infix operator
(+1) <$> [1, 2, 3]       -- [2, 3, 4]

Applicative

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

-- Maybe instance
instance Applicative Maybe where
  pure = Just
  Nothing <*> _       = Nothing
  _ <*> Nothing       = Nothing
  Just f <*> Just x   = Just (f x)

-- Usage
pure (+) <*> Just 3 <*> Just 5  -- Just 8
pure (+3) <*> [1, 2, 3]          -- [4, 5, 6]

-- Applicative style
(+) <$> Just 3 <*> Just 5        -- Just 8

Monad

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

-- Maybe instance
instance Monad Maybe where
  return = Just
  Nothing >>= f = Nothing
  Just x  >>= f = f x

-- Usage
Just 5 >>= \x -> Just (x + 1)    -- Just 6
Nothing >>= \x -> Just (x + 1)   -- Nothing

-- Do notation
addM :: Maybe Int -> Maybe Int -> Maybe Int
addM mx my = do
  x <- mx
  y <- my
  return (x + y)

addM (Just 3) (Just 5)  -- Just 8
addM (Just 3) Nothing   -- Nothing

Lists

List Operations

-- Construction
xs = [1, 2, 3]
ys = 0 : xs              -- [0, 1, 2, 3]
zs = [1, 2] ++ [3, 4]    -- [1, 2, 3, 4]

-- Access
head [1, 2, 3]           -- 1
tail [1, 2, 3]           -- [2, 3]
last [1, 2, 3]           -- 3
init [1, 2, 3]           -- [1, 2]
xs !! 1                  -- 2 (index)

-- Properties
length [1, 2, 3]         -- 3
null []                  -- True
null [1]                 -- False

-- Ranges
[1..5]                   -- [1, 2, 3, 4, 5]
[1,3..10]                -- [1, 3, 5, 7, 9]
['a'..'e']               -- "abcde"

List Comprehensions

-- Basic
[x * 2 | x <- [1..5]]
-- [2, 4, 6, 8, 10]

-- With predicate
[x | x <- [1..10], even x]
-- [2, 4, 6, 8, 10]

-- Multiple generators
[(x, y) | x <- [1, 2], y <- ['a', 'b']]
-- [(1,'a'), (1,'b'), (2,'a'), (2,'b')]

-- Multiple predicates
[x | x <- [1..20], x `mod` 3 == 0, x `mod` 5 == 0]
-- [15]

-- Pattern matching
let xs = [(1, 2), (3, 4), (5, 6)]
[x + y | (x, y) <- xs]
-- [3, 7, 11]

Higher-Order Functions

-- map: Apply function to each element
map (+1) [1, 2, 3]       -- [2, 3, 4]
map show [1, 2, 3]       -- ["1", "2", "3"]

-- filter: Keep elements that match predicate
filter even [1..10]      -- [2, 4, 6, 8, 10]
filter (>5) [1..10]      -- [6, 7, 8, 9, 10]

-- foldl: Left fold
foldl (+) 0 [1, 2, 3]    -- 6
foldl (-) 0 [1, 2, 3]    -- -6

-- foldr: Right fold
foldr (+) 0 [1, 2, 3]    -- 6
foldr (:) [] [1, 2, 3]   -- [1, 2, 3]

-- zipWith: Combine two lists
zipWith (+) [1, 2, 3] [4, 5, 6]  -- [5, 7, 9]
zipWith max [1, 5, 3] [2, 3, 6]  -- [2, 5, 6]

-- takeWhile/dropWhile
takeWhile (<5) [1..10]   -- [1, 2, 3, 4]
dropWhile (<5) [1..10]   -- [5, 6, 7, 8, 9, 10]

Infinite Lists

-- Create infinite lists
naturals = [1..]
evens = [2, 4..]
ones = repeat 1
zeros = replicate 5 0    -- [0, 0, 0, 0, 0]

-- Cycle
cycle [1, 2, 3]          -- [1, 2, 3, 1, 2, 3, ...]

-- Take from infinite lists
take 5 [1..]             -- [1, 2, 3, 4, 5]
take 3 (cycle [1, 2])    -- [1, 2, 1]

-- Iterate
take 5 (iterate (*2) 1)  -- [1, 2, 4, 8, 16]

Functions

Higher-Order Functions

-- Function as parameter
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

applyTwice (+3) 10       -- 16

-- Function composition
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f (g x)

-- Examples
map ((*2) . (+1)) [1, 2, 3]  -- [4, 6, 8]
(not . null) [1, 2]           -- True

-- Dollar operator (low precedence)
($) :: (a -> b) -> a -> b
f $ x = f x

sum $ filter even [1..10]     -- 30
-- Instead of: sum (filter even [1..10])

Lambda Functions

-- Anonymous functions
(\x -> x + 1)
(\x y -> x + y)

-- Usage
map (\x -> x * 2) [1, 2, 3]   -- [2, 4, 6]
filter (\x -> x > 5) [1..10]  -- [6, 7, 8, 9, 10]

-- Multiple parameters
zipWith (\x y -> x + y) [1, 2] [3, 4]  -- [4, 6]

Currying

-- All functions are curried
add :: Int -> Int -> Int
add x y = x + y

-- Partial application
add5 :: Int -> Int
add5 = add 5

add5 3                   -- 8

-- Sections (operators)
(+5) 3                   -- 8
(5+) 3                   -- 8
(/2) 10                  -- 5.0
(2/) 10                  -- 0.2

Point-Free Style

-- With explicit parameters
sum' xs = foldl (+) 0 xs

-- Point-free (no xs)
sum' = foldl (+) 0

-- More examples
isEven x = x `mod` 2 == 0
isEven = (==0) . (`mod` 2)

double xs = map (*2) xs
double = map (*2)

IO and Monads

Basic IO

-- Output
putStrLn :: String -> IO ()
putStr :: String -> IO ()
print :: Show a => a -> IO ()

main = do
  putStrLn "Hello"
  putStr "World"
  print 42

-- Input
getLine :: IO String
getChar :: IO Char

main = do
  putStrLn "What's your name?"
  name <- getLine
  putStrLn $ "Hello, " ++ name

Do Notation

-- Sequencing IO actions
main :: IO ()
main = do
  putStrLn "First"
  putStrLn "Second"
  putStrLn "Third"

-- Binding results
main = do
  line1 <- getLine
  line2 <- getLine
  let combined = line1 ++ line2
  putStrLn combined

-- Return value
main = do
  return ()              -- Returns (), type IO ()

File IO

import System.IO

-- Read entire file
readFile :: FilePath -> IO String
contents <- readFile "file.txt"

-- Write file
writeFile :: FilePath -> String -> IO ()
writeFile "output.txt" "Hello"

-- Append to file
appendFile :: FilePath -> String -> IO ()
appendFile "log.txt" "New entry\n"

-- Handle-based IO
main = do
  handle <- openFile "file.txt" ReadMode
  contents <- hGetContents handle
  putStr contents
  hClose handle

-- With bracket (safer)
import Control.Exception (bracket)

withFile' :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile' path mode f = bracket
  (openFile path mode)
  hClose
  f

Maybe Monad

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

-- Chaining operations
result = do
  a <- safeDiv 10 2     -- Just 5
  b <- safeDiv a 5      -- Just 1
  c <- safeDiv b 0      -- Nothing
  return (c + 1)        -- Nothing

-- Alternative syntax
result = safeDiv 10 2 >>= \a ->
         safeDiv a 5  >>= \b ->
         safeDiv b 0  >>= \c ->
         return (c + 1)

Common Patterns

Maybe

-- Definition
data Maybe a = Nothing | Just a

-- Usage
lookup :: Eq a => a -> [(a, b)] -> Maybe b

-- Pattern matching
safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

-- Functor instance
fmap (+1) (Just 5)       -- Just 6
fmap (+1) Nothing        -- Nothing

-- Applicative
pure (+) <*> Just 3 <*> Just 5  -- Just 8

-- Monad
Just 5 >>= \x -> Just (x * 2)   -- Just 10

Either

-- Definition
data Either a b = Left a | Right b

-- Usage (Left for errors, Right for success)
safeDiv :: Double -> Double -> Either String Double
safeDiv _ 0 = Left "Division by zero"
safeDiv x y = Right (x / y)

-- Pattern matching
describe :: Either String Int -> String
describe (Left err)  = "Error: " ++ err
describe (Right val) = "Value: " ++ show val

-- Chaining
result = do
  a <- safeDiv 10 2     -- Right 5.0
  b <- safeDiv a 0      -- Left "Division by zero"
  c <- safeDiv b 2      -- Not executed
  return c              -- Left "Division by zero"

State Monad

import Control.Monad.State

-- Definition
type State s a = s -> (a, s)

-- Basic operations
get :: State s s
put :: s -> State s ()
modify :: (s -> s) -> State s ()

-- Example: Counter
type Counter = State Int

increment :: Counter ()
increment = modify (+1)

getCount :: Counter Int
getCount = get

-- Running state
runState :: State s a -> s -> (a, s)
evalState :: State s a -> s -> a
execState :: State s a -> s -> s

-- Usage
counter :: Counter Int
counter = do
  increment
  increment
  getCount

runState counter 0       -- (2, 2)
evalState counter 0      -- 2
execState counter 0      -- 2

Reader Monad

import Control.Monad.Reader

-- Definition
type Reader r a = r -> a

-- Basic operations
ask :: Reader r r
local :: (r -> r) -> Reader r a -> Reader r a

-- Example: Configuration
data Config = Config
  { debugMode :: Bool
  , maxRetries :: Int
  }

type App = Reader Config

isDebug :: App Bool
isDebug = asks debugMode

withRetries :: App a -> App a
withRetries = local (\c -> c { maxRetries = 5 })

-- Running reader
runReader :: Reader r a -> r -> a

-- Usage
app :: App String
app = do
  debug <- isDebug
  retries <- asks maxRetries
  return $ "Debug: " ++ show debug ++
           ", Retries: " ++ show retries

config = Config True 3
runReader app config     -- "Debug: True, Retries: 3"

GHCi REPL

Basic Commands

-- Start GHCi
ghci

-- Load file
:load file.hs
:l file.hs

-- Reload
:reload
:r

-- Type information
:type expression
:t expression
:t map                   -- map :: (a -> b) -> [a] -> [b]

-- Kind information
:kind Type
:k Maybe                 -- Maybe :: * -> *

-- Info about identifier
:info identifier
:i Functor

Exploration

-- Show bindings
:show bindings
:show imports

-- Browse module
:browse Data.List
:browse Prelude

-- Search by type
:type-at file.hs line col

-- Documentation
:doc function
:doc map

Settings

-- Set options
:set +s                  -- Show timing
:set +t                  -- Show types
:set prompt "ghci> "     -- Change prompt

-- Multi-line input
:set +m
:{
multiline
input
:}

-- Language extensions
:set -XOverloadedStrings
:set -XFlexibleInstances

Debugging

-- Trace execution
import Debug.Trace

trace :: String -> a -> a
traceShow :: Show a => a -> b -> b

-- Example
factorial n = trace ("n = " ++ show n) $
  if n == 0 then 1 else n * factorial (n - 1)

-- Print during evaluation
factorial 3
-- Prints: n = 3, n = 2, n = 1, n = 0
-- Returns: 6

Tooling

Stack

# Initialize project
stack new myproject
cd myproject

# Build
stack build

# Run
stack exec myproject

# Test
stack test

# REPL
stack ghci

# Dependencies
# Edit stack.yaml and package.yaml
stack build

Cabal

# Initialize project
cabal init

# Build
cabal build

# Run
cabal run

# Test
cabal test

# REPL
cabal repl

# Install dependencies
# Edit myproject.cabal
cabal build

Project Structure

myproject/
├── app/
│   └── Main.hs          # Executable entry point
├── src/
│   └── Lib.hs           # Library code
├── test/
│   └── Spec.hs          # Tests
├── package.yaml         # Stack dependencies
├── stack.yaml           # Stack config
├── myproject.cabal      # Cabal config
└── README.md

Common Packages

# package.yaml (Stack)
dependencies:
  - base >= 4.7 && < 5
  - text # Efficient text
  - bytestring # Byte strings
  - containers # Maps, sets, trees
  - vector # Efficient arrays
  - aeson # JSON parsing
  - mtl # Monad transformers
  - transformers # Base transformers
  - lens # Optics
  - QuickCheck # Property testing
  - hspec # Testing framework

Advanced Types

Polymorphism

-- Parametric polymorphism
id :: a -> a
id x = x

-- Constrained polymorphism
sort :: Ord a => [a] -> [a]

-- Multi-parameter type classes
class Convertible a b where
  convert :: a -> b

instance Convertible Int String where
  convert = show

GADTs

{-# LANGUAGE GADTs #-}

-- Generalized Algebraic Data Types
data Expr a where
  Lit  :: Int -> Expr Int
  Bool :: Bool -> Expr Bool
  Add  :: Expr Int -> Expr Int -> Expr Int
  If   :: Expr Bool -> Expr a -> Expr a -> Expr a

eval :: Expr a -> a
eval (Lit n)      = n
eval (Bool b)     = b
eval (Add e1 e2)  = eval e1 + eval e2
eval (If c t e)   = if eval c then eval t else eval e

Type Families

{-# LANGUAGE TypeFamilies #-}

-- Associated types
class Collection c where
  type Elem c
  insert :: Elem c -> c -> c
  empty :: c

instance Collection [a] where
  type Elem [a] = a
  insert = (:)
  empty = []

-- Data families
data family Array a
data instance Array Int = IntArray [Int]
data instance Array Bool = BoolArray [Bool]

Existential Types

{-# LANGUAGE ExistentialQuantification #-}

-- Hide concrete type
data ShowBox = forall s. Show s => ShowBox s

instance Show ShowBox where
  show (ShowBox s) = show s

-- Heterogeneous list
heteroList :: [ShowBox]
heteroList =
  [ ShowBox 42
  , ShowBox "hello"
  , ShowBox True
  ]

-- Print all
mapM_ print heteroList

Lazy Evaluation

Basics

-- Expressions evaluated only when needed
x = 1 + 2                -- Not evaluated yet
y = x * 3                -- Still not evaluated

-- Forcing evaluation
z = y `seq` y            -- Forces y (and x)

-- Infinite structures work
ones = 1 : ones
take 5 ones              -- [1, 1, 1, 1, 1]

-- Only computes what's needed
head [1..]               -- 1 (doesn't compute rest)

Strict Evaluation

-- Seq: Forces evaluation to WHNF
seq :: a -> b -> b
x `seq` y                -- Evaluates x then returns y

-- Bang patterns
{-# LANGUAGE BangPatterns #-}
f !x = x + 1             -- x evaluated immediately

-- Strict data fields
data Pair = Pair !Int !Int

-- DeepSeq: Forces full evaluation
import Control.DeepSeq
deepseq :: NFData a => a -> b -> b
force :: NFData a => a -> a

Common Pitfalls

-- Space leaks from laziness
-- BAD: Accumulates thunks
sum' xs = foldl (+) 0 xs

-- GOOD: Strict fold
sum' xs = foldl' (+) 0 xs

-- Pattern matching forces evaluation
case x of
  (a, b) -> ...          -- Forces x to WHNF

-- Lazy patterns delay evaluation
case x of
  ~(a, b) -> ...         -- Doesn't force x

Common Gotchas

Indentation

-- WRONG: Inconsistent indentation
main = do
  putStrLn "Hello"
 putStrLn "World"         -- Parse error!

-- RIGHT: Aligned
main = do
  putStrLn "Hello"
  putStrLn "World"

-- RIGHT: Explicit braces
main = do { putStrLn "Hello"; putStrLn "World" }

String Types

-- String is [Char] - inefficient
type String = [Char]

-- Better: Use Text or ByteString
import Data.Text (Text)
import qualified Data.Text as T

import Data.ByteString (ByteString)
import qualified Data.ByteString as BS

-- Enable string literals
{-# LANGUAGE OverloadedStrings #-}
text :: Text
text = "Hello"           -- No conversion needed

Numeric Types

-- Integer division
5 / 2                    -- 2.5 (fractional)
5 `div` 2                -- 2 (integer division)
5 `mod` 2                -- 1 (modulo)

-- Type defaulting
x = 5                    -- Num a => a
x + 3                    -- Still polymorphic
print x                  -- Defaults to Integer

-- Explicit types
x :: Int
x = 5

Partial Functions

-- UNSAFE: Can throw exceptions
head []                  -- Exception!
tail []                  -- Exception!
maximum []               -- Exception!

-- SAFE: Use Maybe variants
safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

-- Or use fold/pattern matching
headOr :: a -> [a] -> a
headOr def []    = def
headOr _ (x:_)   = x

Import/Export

-- Import everything
import Data.List

-- Selective import
import Data.List (sort, nub)

-- Hiding imports
import Data.List hiding (head, tail)

-- Qualified import
import qualified Data.Map as M
M.empty                  -- Data.Map.empty

-- Export list
module MyModule
  ( function1
  , function2
  , TypeName(..)         -- Export type with all constructors
  , TypeName2            -- Export type only
  ) where

Also see