```{-# LANGUAGE  GeneralizedNewtypeDeriving
, NoMonomorphismRestriction
, DeriveGeneric
, BangPatterns #-}
-- | The Atom monad provides functions which convert objects to unique
-- atoms (represented as Ints). Example:
--
-- > example = evalAtom \$ do
-- >   xs <- mapM toAtom "abcabd"
-- >   zs <- mapM fromAtom xs
-- >   return \$ zip zs xs
--
-- >>> example
-- >>> [('a',0),('b',1),('c',2),('a',0),('b',1),('d',3)]

( AtomTable
, Atom
, toAtom
, fromAtom
, maybeToAtom
, empty
, evalAtom
, runAtom
, mapping
)
where
import qualified Data.Map as Map
import qualified Data.IntMap as IntMap
import GHC.Generics (Generic)

-- | @AtomTable@ holds the state necessary for converting to and from
-- @Int@s.
data AtomTable a = T { lastID :: {-# UNPACK #-} !Int
, to     :: !(Map.Map a Int)
, from   :: !(IntMap.IntMap a) }
deriving (Generic)

-- | @Atom@ is a specialized state monad for converting to and from
-- @Int@s.
newtype Atom a r = Atom (State (AtomTable a) r)

-- | @toAtom x@ converts @x@ to a unique @Int@ in the @Atom@ monad
toAtom :: Ord a => a -> Atom a Int
toAtom x = Atom \$ do
let b = x
t <- get
case Map.lookup b (to t) of
Just j -> return \$! j
Nothing -> do
let i = lastID t
i' = i + 1
!t' = t { lastID = i'
, to = Map.insert b  i (to t)
, from = IntMap.insert i b (from t) }
put t'
return \$! lastID t

-- | @maybeToAtom x@ converts @x@ to a unique @Int@ in the @Atom@
maybeToAtom :: Ord a => a -> Atom a (Maybe Int)
maybeToAtom x = Atom \$ do
t <- get
return \$! Map.lookup x . to \$ t

-- | @fromAtom i@ converts the @Int@ @i@ to its corresponding object
fromAtom :: Int -> Atom a a
fromAtom i = Atom \$ do
t <- get
return \$! (from t) IntMap.! i

-- | @empty@ is the initial empty @AtomTable@
empty :: (Ord a) => AtomTable a
empty = T 0 Map.empty IntMap.empty

-- | @runAtom c s@ runs computation c in the Atom monad with the
-- initial @AtomTable@ s.
runAtom :: (Ord a) => Atom a t -> AtomTable a -> (t, AtomTable a)
runAtom (Atom x) s = runState x s

-- | @evalAtom c@ runs computation c in the Atom monad with the empty
-- initial @AtomTable@.
evalAtom :: (Ord a) => Atom a t -> t
evalAtom = fst . flip runAtom empty

-- | The mapping stored in the atom table
mapping :: (Ord a) => AtomTable a -> Map.Map a Int
mapping = to

```