Functional Programming Lecture Revision
Notes
1. Introduction to Functional Programming
Key Concepts:
Functions as first-class citizens
Immutability
Declarative programming style
Emphasis on what to do, not how to do it
Advantages:
Easier to reason about and test
Facilitates parallel processing
Reduces side effects and bugs
2. Pure Functions
Definition:
A function that always produces the same output for the same input and has no side effects.
Characteristics:
Deterministic
No state changes
No I/O operations
Example:
haskell
Copy
-- Pure function
add :: Int -> Int -> Int
add x y = x + y
-- Impure function (due to I/O)
getAndPrintNumber :: IO ()
getAndPrintNumber = do
num <- getLine
print (read num :: Int)
3. Immutability
Definition:
Once a value is created, it cannot be changed.
Benefits:
Prevents unexpected changes
Facilitates concurrent programming
Simplifies debugging
Example:
haskell
Copy
-- Immutable list operation
addToList :: [Int] -> Int -> [Int]
addToList xs x = xs ++ [x] -- Creates a new list
4. Higher-Order Functions
Definition:
Functions that take other functions as arguments or return functions.
Common Higher-Order Functions:
map
filter
fold (reduce)
Examples:
haskell
Copy
-- map example
doubleList :: [Int] -> [Int]
doubleList = map (*2)
-- filter example
evenNumbers :: [Int] -> [Int]
evenNumbers = filter even
-- fold example
sumList :: [Int] -> Int
sumList = foldl (+) 0
5. Recursion
Definition:
A method of solving problems by breaking them down into smaller, self-similar subproblems.
Types:
Direct recursion
Indirect recursion
Tail recursion
Example:
haskell
Copy
-- Factorial function using recursion
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
-- Tail-recursive version
factorialTail :: Integer -> Integer -> Integer
factorialTail acc 0 = acc
factorialTail acc n = factorialTail (acc * n) (n - 1)
6. Pattern Matching
Definition:
A mechanism for checking a value against a pattern and, optionally, binding variables to
successful matches.
Uses:
Function definitions
Case expressions
List comprehensions
Example:
haskell
Copy
-- Pattern matching in function definition
isZero :: Int -> Bool
isZero 0 = True
isZero _ = False
-- Pattern matching in list comprehension
firstElements :: [(a, b)] -> [a]
firstElements xs = [x | (x, _) <- xs]
7. Lazy Evaluation
Definition:
A strategy where expressions are only evaluated when their results are needed.
Benefits:
Can work with infinite data structures
Improves performance for unused computations
Enables separation of concerns in code
Example:
haskell
Copy
-- Infinite list of Fibonacci numbers
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-- Taking first 10 Fibonacci numbers
first10Fibs = take 10 fibs
8. Algebraic Data Types
Definition:
Composite types formed by combining other types.
Types:
Sum types (OR)
Product types (AND)
Example:
haskell
Copy
-- Sum type
data Shape = Circle Float | Rectangle Float Float
-- Product type
data Person = Person { name :: String, age :: Int }
-- Recursive algebraic data type
data Tree a = Leaf a | Node (Tree a) a (Tree a)
9. Type Classes
Definition:
A way to define generic interfaces that provide a common feature set for various types.
Common Type Classes:
Eq
Ord
Show
Functor
Monad
Example:
haskell
Copy
class Sizeable a where
size :: a -> Int
instance Sizeable [a] where
size = length
instance Sizeable (Tree a) where
size (Leaf _) = 1
size (Node left _ right) = 1 + size left + size right
10. Monads
Definition:
A design pattern that allows structuring programs generically while automating away
boilerplate code needed by the program logic.
Key Concepts:
Wrapping computations in a context
Sequencing operations
Handling side effects safely
Example:
haskell
Copy
-- Maybe monad for handling potential failure
safeDivide :: Float -> Float -> Maybe Float
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)
divideAndAdd :: Float -> Float -> Float -> Maybe Float
divideAndAdd x y z = do
result <- safeDivide x y
return (result + z)
11. Functional Data Structures
Characteristics:
Immutable
Often implemented using recursion
May use lazy evaluation
Common Structures:
Lists
Trees
Maps
Example:
haskell
Copy
-- Implementing a functional stack
type Stack a = [a]
push :: a -> Stack a -> Stack a
push x xs = x:xs
pop :: Stack a -> Maybe (a, Stack a)
pop [] = Nothing
pop (x:xs) = Just (x, xs)
12. Function Composition
Definition:
The act of combining two or more functions to produce a new function.
Operator:
In Haskell: (.)
Example:
haskell
Copy
-- Function composition
isEvenLength :: [a] -> Bool
isEvenLength = even . length
-- Equivalent to:
-- isEvenLength xs = even (length xs)
Remember:
Practice writing pure functions
Think recursively
Embrace higher-order functions and composition
Utilize pattern matching for cleaner code
Understand and use appropriate data structures
Functional Programming Revision Notes
1. Immutability
Definition: Immutability refers to the property of an object whose state cannot be modified
after it is created. In functional programming, data structures are typically immutable.
Importance:
Predictability: Easier to reason about code behavior since immutable objects do not
change.
Concurrency: Reduces issues related to shared state, minimizing race conditions.
Functional Purity: Encourages the use of pure functions that rely solely on their
input parameters.
2. First-class Functions
Definition: Functions are treated as first-class citizens, meaning they can be assigned to
variables, passed as arguments, and returned from other functions.
Importance:
Higher-order Functions: Enables functions that can take other functions as
arguments or return them, facilitating abstraction and code reuse.
Flexibility: Allows for dynamic and flexible code structures, enabling patterns like
callbacks and function composition.
Encapsulation: Functions can encapsulate behavior, leading to cleaner and more
modular code.
3. Pure Functions
Definition: A pure function is one that, given the same input, will always return the same
output and has no side effects (does not modify any external state).
Importance:
Testability: Easier to test since output is predictable and does not depend on
external state.
Debugging: Simplifies debugging as you can reason about the function's behavior
without considering external factors.
Referential Transparency: Allows functions to be replaced with their output without
changing the program's behavior.
4. Referential Transparency
Definition: A program is referentially transparent if an expression can be replaced with its
corresponding value without changing the program's behavior.
Importance:
Code Optimization: Enables compilers to optimize code more effectively by safely
replacing expressions with their values.
Simplified Reasoning: Makes reasoning about code easier, leading to clearer and
more maintainable code.
Functional Composition: Facilitates the composition of functions, allowing for more
modular and reusable code.
5. Lazy Evaluation
Definition: A programming technique where expressions are not evaluated until their values
are needed, improving performance and resource management.
Importance:
Efficiency: Improves performance by avoiding unnecessary calculations, especially
when not all data is needed.
Infinite Data Structures: Allows the creation and manipulation of infinite data
structures, as values are computed on demand.
Separation of Concerns: Encourages a separation between the definition of data and
its evaluation, leading to cleaner and more declarative code.
6. Higher-order Functions
Definition: Functions that can take other functions as arguments or return them as results.
Importance:
Abstraction: Allows for more abstract and reusable code.
Function Composition: Enables the combination of simple functions to build more
complex operations.
7. Function Composition
Definition: The process of combining two or more functions to produce a new function.
Importance:
Modularity: Promotes modular design by allowing small, reusable functions to be
combined.
Clarity: Enhances code clarity by expressing complex operations in a more
understandable way.
Summary
Functional programming emphasizes immutability, first-class functions, pure functions, referential
transparency, and lazy evaluation. These concepts contribute to writing efficient, maintainable, and
scalable code. Understanding these principles is crucial for leveraging the full power of functional
programming languages and paradigms.