Functional Programming Techniques in Haskell: A Practical Guide

1. Merging Two Sorted Lists

Task

Merge two sorted lists into one sorted list.

Solution

Base cases handle empty lists; recursive case merges elements based on comparison.

merge :: [Integer] -> [Integer] -> [Integer]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys)
  | x <= y     = x : merge xs (y:ys)
  | otherwise = y : merge (x:xs) ys

2. Splitting a List

Task

Split a list into two approximately equal parts.

Solution

Use pattern matching and recursion to alternate elements into two lists.

split :: [a] -> ([a], [a])
split [] = ([], [])
split [x] = ([x], [])
split (x:y:xs) = (x:xs', y:ys')
  where (xs', ys') = split xs

3. Merge Sort

Task

Implement merge sort using custom split and merge functions.

Solution

Recursively split and merge lists.

mergeSort :: ([a] -> ([a], [a])) -> ([a] -> [a] -> [a]) -> [a] -> [a]
mergeSort _ _ [] = []
mergeSort _ _ [x] = [x]
mergeSort splitter merger xs = merger (mergeSort splitter merger left) (mergeSort splitter merger right)
  where (left, right) = splitter xs

4. Lazy Evaluation

Understanding

Explained lazy evaluation through examples (mySum, myor).

Example

mySum evaluated by creating thunks and postponing arithmetic.

mySum [] = 0
mySum (x:xs) = x + mySum xs

5. Nested Lists

Task

Implement nested lists in Haskell.

Solution

Use recursive data type and functions to handle flattening.

data NestedListItem a = Item a | List [NestedListItem a]
  deriving (Eq, Show)

flatten :: [NestedListItem a] -> [a]
flatten [] = []
flatten (x:xs) = flattenItem x ++ flatten xs

flattenItem :: NestedListItem a -> [a]
flattenItem (Item a) = [a]
flattenItem (List lst) = flatten lst

6. Semigroup and Monoid Instances

Task

Make BoolX an instance of Semigroup and Monoid.

Solution

Implement logical XOR (<>) and identity (mempty).

data BoolX = MkBoolX Bool deriving (Eq, Show)

instance Semigroup BoolX where
    (<>) (MkBoolX a) (MkBoolX b) = MkBoolX (a /= b)

instance Monoid BoolX where
    mempty = MkBoolX False

7. Evaluating Expressions

Task

Evaluate head ( ( (1:[]) ++ (2:[]) ) ++ (3:[]) ).

Solution

Expand the list concatenations step-by-step.

head ( ( (1:[]) ++ (2:[]) ) ++ (3:[]) )
= head ( [1] ++ [2] ++ [3] )
= head ( 1 : ([2] ++ [3]) )
= head ( 1 : 2 : [3] )
= 1

8. Structural Induction

Task

Prove properties about w function using structural induction.

Solution

Base case and induction step for lists.

w [] (N _ a _ ) = [a]
w (O:bt) (N lt a rt) = a : w bt lt
w (I:bt) (N lt a rt) = a : w bt rt

9. Monoid for Summation

Task

Define S as a Monoid to compute sum, sum of squares, and count.

Solution

Implement Semigroup and Monoid instances.

data S = MkS Double Double Int

instance Semigroup S where
    MkS s1 s1_sq n1 <> MkS s2 s2_sq n2 = MkS (s1 + s2) (s1_sq + s2_sq) (n1 + n2)

instance Monoid S where
    mempty = MkS 0 0 0

10. Handling UNIX PATH Environment Variable

Task

Split a string by colons and delete a specific string from a list.

Solution

Implement splitOnce, split, delete, and glue functions.

splitOnce :: String -> (String, String)
splitOnce "" = ("", "")
splitOnce (':' : rest) = ("", rest)
splitOnce str = splitHelper str ""

splitHelper :: String -> String -> (String, String)
splitHelper "" acc = (reverse acc, "")
splitHelper (':' : rest) acc = (reverse acc, rest)
splitHelper (x : xs) acc = splitHelper xs (x : acc)

split :: String -> [String]
split "" = []
split str =
    let (first, rest) = splitOnce str
    in first : split rest

delete :: String -> [String] -> [String]
delete s = filter (/= s)

import Data.List (intercalate)

glue :: [String] -> String
glue = intercalate ":"

11. AVL Tree Insertions

Task

Implement insertion for an AVL tree and ensure it remains balanced.

Solution

Define helper functions for rotations, balancing, and updating node heights.

module AVL(insert) where

import AVLDef

-- Helper Functions for Rotations and Balancing
updateHeight :: AVL k -> AVL k
updateHeight Empty = Empty
updateHeight node@(Node { avlLeft = l, avlRight = r }) =
    node { avlHeight = 1 + max (height l) (height r) }

-- Left Rotation
rotateLeft :: AVL k -> AVL k
rotateLeft Node{avlLeft, avlKey, avlRight = Node{avlLeft = b, avlKey = y, avlRight = c, avlHeight = _}, avlHeight = _} =
    let newLeft = updateHeight (Node avlLeft avlKey b 0) -- Construct new left child and update its height
        newRoot = updateHeight (Node newLeft y c 0)     -- Construct new root and update its height
    in newRoot
rotateLeft n = n

-- Right Rotation
rotateRight :: AVL k -> AVL k
rotateRight Node{avlLeft = Node{avlLeft = a, avlKey = x, avlRight = b, avlHeight = _}, avlKey, avlRight, avlHeight = _} =
    let newRight = updateHeight (Node b avlKey avlRight 0) -- Construct new right child and update its height
        newRoot = updateHeight (Node a x newRight 0)      -- Construct new root and update its height
    in newRoot
rotateRight n = n

-- Balance the tree at a node
balance :: AVL k -> AVL k
balance node@(Node { avlLeft = l, avlRight = r, avlHeight = _ })
  | balanceFactor node > 1 && balanceFactor l >= 0 = rotateRight node
  | balanceFactor node > 1 && balanceFactor l < 0  = rotateRight $ node { avlLeft = rotateLeft l }
  | balanceFactor node < -1 && balanceFactor r <= 0 = rotateLeft node
  | balanceFactor node < -1 && balanceFactor r > 0  = rotateLeft $ node { avlRight = rotateRight r }
  | otherwise = updateHeight node
balance node = node

-- Insert Function
insert :: Ord k => k -> AVL k -> AVL k
insert key Empty = singleton key
insert key node@(Node { avlLeft = l, avlKey = k, avlRight = r })
  | key < k = balance $ node { avlLeft = insert key l }
  | key > k = balance $ node { avlRight = insert key r }
  | otherwise = node  -- No duplicates allowed