--
-- Adapted from the program "infer", believed to have been originally
-- authored by Philip Wadler, and used in the nofib benchmark suite
-- since at least the late 90s.
--

module FiniteMap
      (FM, emptyFM, unitFM, extendFM, makeFM, unmakeFM, thenFM, plusFM,
       lookupFM, lookupElseFM, mapFM, domFM, ranFM, disjointFM) where

data  FM a b  =  MkFM [(a,b)]
emptyFM                               ::  FM a b
emptyFM                               =   MkFM []
unitFM                                ::  a -> b -> FM a b
unitFM a b                            =   MkFM [(a,b)]
extendFM                              ::  FM a b -> a -> b -> FM a b
extendFM (MkFM abs) a b               =   MkFM ((a,b) : abs)
makeFM                                ::  [(a,b)] -> FM a b
makeFM abs                            =   MkFM abs
unmakeFM                              ::  FM a b -> [(a,b)]
unmakeFM (MkFM abs)                   =   abs
thenFM                                ::  FM a b -> FM a b -> FM a b
(MkFM abs1) `thenFM` (MkFM abs2)      =   MkFM (abs2 ++ abs1)
plusFM                                ::  (Eq a) => FM a b -> FM a b -> FM a b
f `plusFM` g  |  f `disjointFM` g     =   f `thenFM` g
lookupFM                              ::  (Eq a) => FM a b -> a -> b
lookupFM f a                          =   lookupElseFM (error "lookup") f a
lookupElseFM                          ::  (Eq a) => b -> FM a b -> a -> b
lookupElseFM b (MkFM abs) a           =   head (  [ b' | (a',b') <- abs, a==a' ]
                                               ++ [ b ] )
mapFM                                 ::  (b -> c) -> FM a b -> FM a c
mapFM h (MkFM abs)                    =   MkFM [ (a, h b) | (a,b) <- abs ]
domFM                                 ::  FM a b -> [a]
domFM (MkFM abs)                      =   [ a | (a,b) <- abs ]
ranFM                                 ::  FM a b -> [b]
ranFM (MkFM abs)                      =   [ b | (a,b) <- abs ]
disjointFM                            ::  (Eq a) => FM a b -> FM a b -> Bool
f `disjointFM` g                      =   domFM f `disjoint` domFM g
disjoint                              ::  (Eq a) => [a] -> [a] -> Bool
xs `disjoint` ys                      =   and [ not (x `elem` ys) | x <- xs ]
