Skip to content

Restricting Exports

Consider an implementation of an \((a,b)\)-tree data structure. We would probably put it into a module called Data.ABTree:

Data/ABTree.hs
module Data.ABTree where

import Data.List (intercalate)

data ABNode t = ABLeaf t
              | ABNode Int t [ABNode t]

data ABTree t = ABTree Int Int (Maybe (ABNode t))

abKey :: ABNode t -> t
abKey (ABLeaf   x)   = x
abKey (ABNode _ x _) = x

abMakeNode :: Int -> [ABNode t] -> ABNode t
abMakeNode deg vs = ABNode deg (abKey $ head vs) vs

abEmptyTree :: Int -> Int -> ABTree t
abEmptyTree a b = ABTree a b Nothing

abElem :: Ord t => t -> ABTree t -> Bool
abElem = flip $ abOp
    (\_ _ _       -> False )
    (\x y _       -> x == y)

abInsert :: Ord t => ABTree t -> t -> ABTree t
abInsert = abOp
    (\a b y            -> ABTree a b (Just (ABLeaf y)))
    (\x y k            -> k $ [ABLeaf x | x < y] ++ [ABLeaf y] ++ [ABLeaf x | x > y])

abDelete :: Ord t => ABTree t -> t -> ABTree t
abDelete = abOp
    (\a b _            -> ABTree a b Nothing)
    (\x y k            -> k [ABLeaf x | x /= y])

abOp :: Ord t
     => (Int -> Int -> t -> b)
     -> (t -> t -> (([ABNode t] -> ABTree t) -> b))
     -> ABTree t -> t -> b
abOp emptyOp _      (ABTree a b Nothing)  y = emptyOp a b y
abOp _       leafOp (ABTree a b (Just r)) y = subtreeOp r (abMakeTree a b)
  where
    subtreeOp (ABLeaf x)        k = leafOp x y k
    subtreeOp (ABNode deg _ vs) k = subtreeOp m (\x -> k $ abRebalanceChildren a b deg ls x rs)
      where
        (ls, m, rs) = split (\v -> abKey v <= y) vs

abMakeTree :: Int -> Int -> [ABNode t] -> ABTree t
abMakeTree a b rs = case rs of
    []                             -> ABTree a b Nothing
    [ABNode _ _ [r]]               -> ABTree a b (Just r)
    [r@(ABNode deg _ _)] | deg > b -> ABTree a b (Just $ abMakeNode 2 $ abSplitNode r)
    [r]                            -> ABTree a b (Just r)
    rs                             -> ABTree a b (Just $ abMakeNode 2 rs)

abRebalanceChildren :: Int -> Int -> Int -> [ABNode t] -> [ABNode t] -> [ABNode t] -> [ABNode t]
abRebalanceChildren a b deg ls ms rs = case ms of
    [m@(ABNode mdeg _ _)]
        | mdeg > b -> [abMakeNode (deg + 1) (unsplit ls (abSplitNode m ++ rs))]
        | mdeg < a -> case (ls, rs) of
            (_, r:rs') -> abRebalanceChildren a b (deg - 1) ls  [abMergeNodes m r] rs'
            (l:ls', _) -> abRebalanceChildren a b (deg - 1) ls' [abMergeNodes l m] rs
            _          -> error "BUG: Internal node had degree 1"
    _              -> [abMakeNode (deg + length ms - 1) (unsplit ls (ms ++ rs))]

abSplitNode :: ABNode t -> [ABNode t]
abSplitNode (ABLeaf _)        = error "BUG: abSplitNode called on a leaf"
abSplitNode (ABNode deg _ vs) = [abMakeNode ldeg ls, abMakeNode rdeg rs]
  where
    ldeg     = deg `div` 2
    rdeg     = deg - ldeg
    (ls, rs) = splitAt ldeg vs

abMergeNodes :: ABNode t -> ABNode t -> ABNode t
abMergeNodes (ABNode ldeg _ ls) (ABNode rdeg _ rs) = abMakeNode (ldeg + rdeg) (ls ++ rs)
abMergeNodes _                  _                  = error "BUG: Called abMergeNodes on a leaf"

split :: (a -> Bool) -> [a] -> ([a], a, [a])
split pred = go []
  where
    go []      (r:rs') = go [r] rs'
    go (l:ls') []      = (ls', l, [])
    go ls@(l:ls') rs@(r:rs')
        | pred r      = go (r:ls) rs'
        | otherwise   = (ls', l, rs)

unsplit :: [a] -> [a] -> [a]
unsplit = flip $ foldl (flip (:))

The public interface to an \((a,b)\)-tree consists of the data type ABTree, and functions abEmptyTree, abElem, abInsert, and abDelete. All other functions in this module should be considered implementation details of these functions that should not be shared with other modules. By changing the module header to

module Data.ABTree
    ( ABTree
    , abEmptyTree
    , abElem
    , abInsert
    , abDelete
    ) where

we state that only the ABTree type and the four functions in this list should be exported. This list after the module name is called an export list. Any data type, type class or function not included in this list is now private to our module.