dawgdic-1.0.0: src/Data/DAWG/Internal/DAWGBuilder.hs
{-|
Module: Data.DAWG.Internal.DAWGBuilder
Description: Exports dawg builder as well as its internal API.
Copyright: (c) Andrey Prokopenko, 2025
License: BSD-3-Clause
Stability: experimental
-}
module Data.DAWG.Internal.DAWGBuilder where
import Control.Monad (forM_, when, unless)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Data.Bit (Bit (..))
import Data.Bits
import Data.Char
import Data.Maybe (fromMaybe)
import Data.Primitive.MutVar
import Data.Vector (Vector)
import GHC.Stack (HasCallStack)
import Prelude hiding (init)
import Data.Primitive.PrimArray.Combinators
import Data.DAWG.Internal.BaseType
import Data.DAWG.Internal.BaseUnit (BaseUnit (..), base, hasSibling, isState)
import Data.DAWG.Internal.DAWG
(DAWG (..), BasePool (..), BitPool (..), FlagPool (..), LabelPool (..))
import Data.DAWG.Internal.DAWGUnit (DAWGUnit (..))
import Data.DAWG.Internal.Stack
import qualified Data.DAWG.Internal.BaseUnit as BaseUnit
import qualified Data.DAWG.Internal.DAWGUnit as DawgUnit
import qualified Data.Primitive.PrimArray.Utils as A
import qualified Data.Vector as Vector
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Generic.Mutable as V
-- ** DAWG Builder
-- | A mutable builder of 'Data.DAWG.Internal.DAWG.DAWG'.
newtype DAWGBuilder m = DBRef { getDBRef :: MutVar (PrimState m) (DAWGBuilder_ m) }
-- | Builder of DAWG. Do not access directly. Use 'DAWGBuilder' instead.
data DAWGBuilder_ m = DAWGBuilder
{ dawgBuilderBasePool :: ObjectPool (PrimState m) BaseUnit -- ^ Pool of base units.
, dawgBuilderLabelPool :: ObjectPool (PrimState m) UCharType -- ^ Pool of labels.
, dawgBuilderFlagPool :: ObjectPool (PrimState m) Bit -- ^ Pool of bit flags to store merges.
, dawgBuilderUnitPool :: ObjectPool (PrimState m) DAWGUnit -- ^ Pool of dawg units.
, dawgBuilderHashTable :: ObjectPool (PrimState m) BaseType -- ^ Supportive pool of hashes.
, dawgBuilderUnfixedUnits :: Stack (PrimState m) -- ^ Supportive stack to keep track of for unfixed units.
, dawgBuilderUnusedUnits :: Stack (PrimState m) -- ^ Supportive stack to keep track of unused units
, dawgBuilderRefs :: !(IntArray (PrimState m)) -- ^ Array which holds a few numbers: 'numOfStates', 'numOfMergedTransitions', 'numOfMergingStates'.
}
numOfStates, numOfMergedTransitions, numOfMergingStates :: Int
-- | Use it as index for 'dawgBuilderRefs' to get number of states.
numOfStates = 0
{-# INLINE numOfStates #-}
-- | Use it as index for 'dawgBuilderRefs' to get number of merged transitions.
numOfMergedTransitions = 1
{-# INLINE numOfMergedTransitions #-}
-- | Use it as index for 'dawgBuilderRefs' to get number of merging states.
numOfMergingStates = 2
{-# INLINE numOfMergingStates #-}
-- | Creates a new empty 'DAWGBuilder'.
new :: PrimMonad m => m (DAWGBuilder m)
new = do
dawgBuilderHashTable <- V.new 0
dawgBuilderBasePool <- V.new 0
dawgBuilderLabelPool <- V.new 0
dawgBuilderFlagPool <- V.new 0
dawgBuilderUnitPool <- V.new 0
dawgBuilderUnfixedUnits <- StackRef <$> newMutVar EndOfStack
dawgBuilderUnusedUnits <- StackRef <$> newMutVar EndOfStack
dawgBuilderRefs <- A.replicate 3 0
dawgBuilderRefs <~ numOfStates $ 1
let db = DAWGBuilder{..}
getDBRef <- newMutVar db
pure DBRef {..}
{-# INLINE new #-}
-- | Initializes empty 'DAWGBuilder' with the root index and the beginning of the word.
init
:: PrimMonad m
=> DAWGBuilder m -> m ()
init dbref = do
do
let initHtSize = 1 .<<. 8
resize dbref initHtSize
!_ <- allocateUnit dbref
!_ <- allocateTransition dbref
db <- readMutVar (getDBRef dbref)
dawgBuilderUnitPool db <~~ 0 $ DawgUnit.setLabel DawgUnit.empty 0xFF
push 0 (dawgBuilderUnfixedUnits db)
{-# INLINE init #-}
-- | Inserts a word with optional value associated to it into 'DAWGBuilder'.
-- Pass 'Nothing' as value if there is no value associated with the word.
-- Returns 'False' if word was not inserted.
insert
:: HasCallStack
=> PrimMonad m
=> Vector Char
-> Maybe ValueType
-> DAWGBuilder m
-> m Bool
insert ks mValue db = do
let v = fromMaybe 0 mValue
if not (Vector.null ks || ks == Vector.singleton '\0' || v < 0)
then do
let ks' = Vector.takeWhile (/= '\0') ks
l = Vector.length ks'
insertKey ks' l v db
else pure False
{-# INLINE insert #-}
-- | Like 'insertKey' but it also performs input validation.
-- Returns 'False' if word was not inserted.
insertWithLength
:: HasCallStack
=> PrimMonad m
=> Vector Char
-> Int
-> ValueType
-> DAWGBuilder m
-> m Bool
insertWithLength ks l v db =
if not (Vector.null ks || ks == Vector.singleton '\0' || v < 0)
then if (Vector.notElem '\0' ks)
then insertKey ks l v db
else pure False
else pure False
{-# INLINE insertWithLength #-}
-- | Inserts a word (@key@) with its @length@ and associated @value@ into 'DAWGBuilder'.
-- Pass @0@ if there is no value associated with the word.
-- Returns 'False' if word was not inserted.
insertKey
:: HasCallStack
=> PrimMonad m
=> Vector Char -- ^ Entire word as vector of keys.
-> Int -- ^ Prefix length.
-> ValueType -- ^ Value.
-> DAWGBuilder m
-> m Bool
insertKey ks l v dbref@DBRef{..} = do
do
db <- readMutVar getDBRef
let !htsize = V.length $ dawgBuilderHashTable db
when (htsize == 0) $ init dbref
-- Find separate unit
let findSeparateUnit (!ix, !keyPos)
| keyPos > fromIntegral l = pure $ Just (ix, keyPos)
| otherwise = do
db <- readMutVar getDBRef
!u0 <- dawgBuilderUnitPool db !~ ix
let !childIx = DawgUnit.child u0
if childIx == 0
then pure $ Just (ix, keyPos)
else do
let !keyLabel = if keyPos < fromIntegral l then ks Vector.! keyPos else '\0'
!u' <- dawgBuilderUnitPool db !~ childIx
let !unitLabel = DawgUnit.label u'
if ord keyLabel < fromIntegral unitLabel
then pure Nothing
else if ord keyLabel > fromIntegral unitLabel
then do
dawgBuilderUnitPool db <~~ childIx $ DawgUnit.setHasSibling u' True
fixUnits childIx dbref
pure $ Just (ix, keyPos)
else findSeparateUnit (childIx, succ keyPos)
addNewUnit (!ix, !keyPos)
| keyPos > fromIntegral l = pure (ix, keyPos)
| otherwise = do
let keyLabel = if keyPos < fromIntegral l then ks Vector.! keyPos else '\0'
childIx <- allocateUnit dbref
ndb <- readMutVar getDBRef
!u <- dawgBuilderUnitPool ndb !~ ix
let setState !u' = if DawgUnit.child u == 0
then DawgUnit.setIsState u' True
else u'
!nu = setState
$! flip DawgUnit.setSibling (DawgUnit.child u)
$! flip DawgUnit.setLabel (fromIntegral $! ord keyLabel)
$! DawgUnit.empty
dawgBuilderUnitPool ndb <~~ childIx $ nu
dawgBuilderUnitPool ndb <~~ ix $ DawgUnit.setChild u childIx
push childIx (dawgBuilderUnfixedUnits ndb)
addNewUnit (childIx, succ keyPos)
findSeparateUnit (0, 0) >>= \case
Nothing -> pure False
Just (!ix, !keyPos) -> do
(!lastIx, _) <- addNewUnit (ix, keyPos)
ndb <- readMutVar getDBRef
lu <- dawgBuilderUnitPool ndb !~ lastIx
dawgBuilderUnitPool ndb <~~ lastIx $ DawgUnit.setChild lu (fromIntegral v)
pure True
-- | Generates 'Data.DAWG.Internal.DAWG.DAWG' out of 'DAWGBuilder'.
-- Once this function is called, 'DAWGBuilder' must not be used anymore.
freeze
:: HasCallStack
=> PrimMonad m => DAWGBuilder m -> m DAWG
freeze dbref@DBRef{..} = do
do
db0 <- readMutVar getDBRef
let !htsize = V.length $ dawgBuilderHashTable db0
when (htsize == 0) $ init dbref
fixUnits 0 dbref
db <- readMutVar getDBRef
unit0 <- dawgBuilderUnitPool db !~ 0
dawgBuilderBasePool db <~~ 0 $ BaseUnit $ DawgUnit.base unit0
dawgBuilderLabelPool db <~~ 0 $ DawgUnit.label unit0
fbasePool <- VG.unsafeFreeze $ dawgBuilderBasePool db
flabelPool <- VG.unsafeFreeze $ dawgBuilderLabelPool db
fflagPool <- VG.unsafeFreeze $ dawgBuilderFlagPool db
fnumOfStates <- dawgBuilderRefs db ! numOfStates
fnumOfMergedTransitions <- dawgBuilderRefs db ! numOfMergedTransitions
fnumOfMergingStates <- dawgBuilderRefs db ! numOfMergingStates
let numOfTransitions = VG.length fbasePool - 1
numOfMergedStates = numOfTransitions + fnumOfMergedTransitions + 1 - fnumOfStates
pure DAWG
{ dawgBasePool = BasePool fbasePool
, dawgLabelPool = LabelPool flabelPool
, dawgFlagPool = FlagPool $ BitPool fflagPool
, dawgNumOfStates = fromIntegral fnumOfStates
, dawgNumOfMergedTransitions = fromIntegral fnumOfMergedTransitions
, dawgNumOfMergedStates = fromIntegral numOfMergedStates
, dawgNumOfMergingStates = fromIntegral fnumOfMergingStates
}
{-# INLINE freeze #-}
-- | Builds entire 'Data.DAWG.Internal.DAWG.DAWG' from a lexicon. Lexicon *must be* sorted.
fromAscList :: HasCallStack => PrimMonad m => [String] -> m DAWG
fromAscList lexicon = do
db <- new
forM_ lexicon \w -> do
insert (Vector.fromList w) Nothing db
freeze db
{-# INLINE fromAscList #-}
-- ** Helpers
-- | Gets a unit from an object pool.
allocateUnit
:: HasCallStack
=> PrimMonad m
=> DAWGBuilder m -> m BaseType
allocateUnit DBRef{..} = do
!db <- readMutVar getDBRef
(!index, !ndb) <- readMutVar (getStackRef $ dawgBuilderUnusedUnits db) >>= \case
-- no unused units left
EndOfStack -> do
newUnitPool <- V.grow (dawgBuilderUnitPool db) 1
let !index = pred $ V.length newUnitPool
!nextDb = db { dawgBuilderUnitPool = newUnitPool }
dawgBuilderUnitPool nextDb <~~ fromIntegral index $ DawgUnit.empty
pure (fromIntegral index, nextDb)
Elem !index !stack -> do
writeMutVar (getStackRef $ dawgBuilderUnusedUnits db) stack
pure (index, db)
writeMutVar getDBRef ndb
pure index
-- | Adds free unit index to the stack of unused units.
freeUnit :: HasCallStack => PrimMonad m => DAWGBuilder_ m -> BaseType -> m ()
freeUnit db !ix = do
prevStack <- readMutVar (getStackRef $ dawgBuilderUnusedUnits db)
let !stack = Elem ix prevStack
writeMutVar (getStackRef $ dawgBuilderUnusedUnits db) stack
{-# INLINE freeUnit #-}
-- | Gets a transition from object pools.
allocateTransition
:: HasCallStack
=> PrimMonad m => DAWGBuilder m -> m BaseType
allocateTransition DBRef{..} = do
db <- readMutVar getDBRef
newFlagPool <- V.grow (dawgBuilderFlagPool db) 1
newFlagPool <~~ fromIntegral (V.length newFlagPool - 1) $ 0
newBasePool <- V.grow (dawgBuilderBasePool db) 1
newBasePool <~~ fromIntegral (V.length newBasePool - 1) $ BaseUnit.empty
newLabelPool <- V.grow (dawgBuilderLabelPool db) 1
newLabelPool <~~ fromIntegral (V.length newLabelPool - 1) $ 0
let !lastIx = V.length newLabelPool - 1
!ndb = db
{ dawgBuilderFlagPool = newFlagPool
, dawgBuilderBasePool = newBasePool
, dawgBuilderLabelPool = newLabelPool
}
writeMutVar getDBRef ndb
pure (fromIntegral lastIx)
-- | Recursively fix units starting from a given index.
fixUnits
:: HasCallStack
=> PrimMonad m
=> BaseType -> DAWGBuilder m -> m ()
fixUnits !index dbref@DBRef{..} = do
let countSiblings !acc 0 = pure acc
countSiblings !(acc :: Int) !ix = do
db <- readMutVar getDBRef
unit' <- dawgBuilderUnitPool db !~ ix
countSiblings (succ acc) (DawgUnit.sibling unit')
-- this operation potentially mutates dawg builder:
-- re-read its content after its usage
getTransitionIndex !siblings !ix !tix = if ix < siblings
then do
!nextTix <- allocateTransition dbref
getTransitionIndex siblings (succ ix) nextTix
else pure tix
goBaseLabel (0 :: BaseType) !tix = pure tix
goBaseLabel !ix !tix = do
db <- readMutVar getDBRef
!unit' <- dawgBuilderUnitPool db !~ ix
let !nextIx = DawgUnit.sibling unit'
dawgBuilderBasePool db <~~ tix $ (BaseUnit $ DawgUnit.base unit')
dawgBuilderLabelPool db <~~ tix $ DawgUnit.label unit'
goBaseLabel nextIx (pred tix)
deleteFixedUnits !_ 0 = pure ()
deleteFixedUnits !db !current = do
!unit' <- dawgBuilderUnitPool db !~ current
let !next = DawgUnit.sibling unit'
freeUnit db current
deleteFixedUnits db next
goStack = do
db <- readMutVar getDBRef
top (dawgBuilderUnfixedUnits db) >>= \case
Nothing -> pure ()
Just !unfixedIx -> do
unless (unfixedIx == index) do
pop (dawgBuilderUnfixedUnits db)
numOfStates' <- dawgBuilderRefs db ! numOfStates
let !htsize = V.length $ dawgBuilderHashTable db
when (numOfStates' >= htsize - (htsize .>>. 2)) do
expandHashTable dbref
numOfSiblings <- countSiblings 0 unfixedIx
(hashId, matchedIx) <- findUnit unfixedIx dbref
nextMatchedIx <- if matchedIx /= 0
then do
prevNumOfMergedTransitions <- dawgBuilderRefs db ! numOfMergedTransitions
let !nextNumOfMergedTransitions = prevNumOfMergedTransitions + numOfSiblings
dawgBuilderRefs db <~ numOfMergedTransitions $ nextNumOfMergedTransitions
flag' <- dawgBuilderFlagPool db !~ matchedIx
-- Records a merging state.
when (flag' == 0) do
prevMergingStates <- dawgBuilderRefs db ! numOfMergingStates
dawgBuilderRefs db <~ numOfMergingStates $ prevMergingStates + 1
dawgBuilderFlagPool db <~~ matchedIx $ 1
pure matchedIx
else do
!startTransitionIndex <- getTransitionIndex numOfSiblings 0 0
-- re-read from mutable variable
ndb <- readMutVar getDBRef
!transitionIndex <- goBaseLabel unfixedIx startTransitionIndex
let newMatchedIx = succ transitionIndex
dawgBuilderHashTable ndb <~~ hashId $ newMatchedIx
prevStates <- dawgBuilderRefs ndb ! numOfStates
dawgBuilderRefs ndb <~ numOfStates $ prevStates + 1
pure newMatchedIx
-- Delete fixed units
ndb <- readMutVar getDBRef
deleteFixedUnits ndb unfixedIx
top (dawgBuilderUnfixedUnits ndb) >>= \case
Nothing -> pure ()
Just !nextUnfixedIx -> do
dawgBuilderUnitPool ndb !<~~ nextUnfixedIx
$! flip DawgUnit.setChild nextMatchedIx
writeMutVar getDBRef ndb
goStack
goStack
readMutVar getDBRef >>= \ldb -> do
pop (dawgBuilderUnfixedUnits ldb)
writeMutVar getDBRef ldb
-- | Expands supportive hash table by doubling its size.
expandHashTable
:: PrimMonad m
=> DAWGBuilder m -> m ()
expandHashTable dbref@DBRef{..} = do
do
db0 <- readMutVar getDBRef
let !htsize = V.length $ dawgBuilderHashTable db0
!newSize = htsize .<<. 1
newHt <- V.replicate newSize 0
let !db1 = db0 { dawgBuilderHashTable = newHt }
writeMutVar getDBRef db1
db <- readMutVar getDBRef
let go !ix !base'
| ix == 0 = pure ()
| otherwise = do
label' <- dawgBuilderLabelPool db !~ fromIntegral ix
when (label' == 0 || isState base') do
let !bix = fromIntegral ix
!hashId <- findTransition bix dbref
dawgBuilderHashTable db <~~ fromIntegral hashId $ fromIntegral ix
V.iforM_ (dawgBuilderBasePool db) go
{-# INLINE expandHashTable #-}
-- | Finds suitable transition index.
findTransition
:: PrimMonad m
=> BaseType -> DAWGBuilder m -> m BaseType
findTransition !index dbref@DBRef{..} = do
db <- readMutVar getDBRef
let !htsize = V.length $ dawgBuilderHashTable db
!unit' <- hashTransition index dbref
let !startHashId = unit' `mod` fromIntegral htsize
go !hid = do
transitionId <- dawgBuilderHashTable db !~ hid
if transitionId == 0
then pure hid
else do
let !htsize' = V.length $ dawgBuilderHashTable db
go (succ hid `mod` fromIntegral htsize')
go startHashId
{-# INLINE findTransition #-}
-- | Finds suitable hash id as well as transition index for the given unit.
findUnit
:: PrimMonad m
=> BaseType -> DAWGBuilder m -> m (BaseType, BaseType)
findUnit !unitIndex dbref@DBRef{..} = do
db <- readMutVar getDBRef
let !htsize0 = V.length $ dawgBuilderHashTable db
!unit' <- hashUnit unitIndex dbref
let !hashId = unit' `mod` fromIntegral htsize0
findInTable !hid = do
let !htsize = V.length $ dawgBuilderHashTable db
transitionId <- dawgBuilderHashTable db !~ hid
if transitionId == 0
then pure (hid, 0)
else areEqual unitIndex transitionId dbref >>= \case
True -> pure (hid, transitionId)
False -> findInTable (succ hid `mod` fromIntegral htsize)
findInTable hashId
{-# INLINE findUnit #-}
-- | Checks whether unit index matches with transition index or not.
areEqual
:: PrimMonad m
=> BaseType -> BaseType -> DAWGBuilder m -> m Bool
areEqual !unitIndex !transitionIndex !DBRef{..} = do
db <- readMutVar getDBRef
!startUnit <- dawgBuilderUnitPool db !~ unitIndex
let !startIx = DawgUnit.sibling startUnit
-- Mismatch: at this point there should be no siblings in associated base pool
let goUnit !tix 0 = pure (tix, False)
goUnit !tix !uix = do
base' <- dawgBuilderBasePool db !~ tix
let !baseHasSibling = hasSibling base'
if not baseHasSibling
then pure (tix, True)
else do
!unit <- dawgBuilderUnitPool db !~ uix
goUnit (succ tix) (DawgUnit.sibling unit)
goBack !tix 0 = pure (tix, True)
goBack !tix !(uix :: BaseType) = do
!unit' <- dawgBuilderUnitPool db !~ uix
!base' <- dawgBuilderBasePool db !~ tix
!label' <- dawgBuilderLabelPool db !~ tix
if DawgUnit.base unit' /= base base' || DawgUnit.label unit' /= label'
then pure (tix, False)
else goBack (pred tix) (fromIntegral $ DawgUnit.sibling unit')
(outTransitionIndex, inTransitionMismatch) <-
goUnit (fromIntegral transitionIndex) (fromIntegral startIx)
if inTransitionMismatch
then pure False
else snd <$> goBack outTransitionIndex (fromIntegral unitIndex)
{-# INLINE areEqual #-}
-- | Calculates a hash value from a transition.
hashTransition :: forall m. PrimMonad m => BaseType -> DAWGBuilder m -> m BaseType
hashTransition !ix DBRef{..} = do
db <- readMutVar getDBRef
let go !hv (0 :: BaseType) = pure hv
go !hv !ix' = do
!bu <- dawgBuilderBasePool db !~ ix'
let !itHasSibling = hasSibling bu
!base' = base bu
!label' <- fromIntegral @_ @BaseType <$> dawgBuilderLabelPool db !~ ix'
let !newHashValue = hv .^. hashBaseType ((label' .<<. 24) .^. fromIntegral base')
if itHasSibling then go newHashValue (succ ix') else pure newHashValue
fromIntegral <$> go 0 ix
{-# INLINE hashTransition #-}
-- | Calculates a hash value from a unit.
hashUnit
:: forall m. PrimMonad m
=> BaseType -> DAWGBuilder m -> m BaseType
hashUnit !ix DBRef{..} = do
db <- readMutVar getDBRef
let go :: BaseType -> BaseType -> m BaseType
go !hv 0 = pure hv
go !hv !ix' = do
!u <- dawgBuilderUnitPool db !~ ix'
let !base' = DawgUnit.base u
!label' = DawgUnit.label u
!newHashValue = hv .^. fromIntegral
(hashBaseType ((fromIntegral label' .<<. 24) .^. fromIntegral base'))
!next = fromIntegral $! DawgUnit.sibling u
go newHashValue next
go 0 (fromIntegral ix)
{-# INLINE hashUnit #-}
-- | Dump builder to stdout.
dump :: DAWGBuilder IO -> IO ()
dump DBRef{..} = do
db <- readMutVar getDBRef
let !bs = V.length $ dawgBuilderBasePool db
!ls = V.length $ dawgBuilderLabelPool db
!us = V.length $ dawgBuilderUnitPool db
!ms = maximum [bs, ls, us]
putStrLn $ concat [ "b(", show bs, ")\tl(", show ls, ")\tu(", show us, ")" ]
forM_ [0 .. ms - 1] \i -> do
!b <- fromMaybe (BaseUnit 0) <$> (V.readMaybe (dawgBuilderBasePool db) i)
!l <- fromMaybe 0 <$> (V.readMaybe (dawgBuilderLabelPool db) i)
!u <- fromMaybe DawgUnit.empty <$> (V.readMaybe (dawgBuilderUnitPool db) i)
putStrLn $ concat
[ show b, "\t", show (chr $ fromIntegral l), " ", show l, "\t", show u
]
let topStr stack = readMutVar (getStackRef $ stack db) >>= \case
EndOfStack -> pure ""
Elem el _ -> pure $ show el
unfixed <- topStr dawgBuilderUnfixedUnits
unused <- topStr dawgBuilderUnusedUnits
let !htsize = V.length $ dawgBuilderHashTable db
putStrLn $ "unfixed : " <> unfixed
putStrLn $ "unused : " <> unused
putStrLn $ "ht.size : " <> show htsize
-- ** HashTable helper
-- | Resizes a vector to a given size.
--
-- * If new size is lesser than the table one, it shrinks the table.
-- * If new size is greater than the table one, it allocates empty units in the vector to fit new size.
-- * Otherwise, it does not do anything.
--
-- See it as an equivalent to @std::vector.resize()@.
resize :: PrimMonad m => DAWGBuilder m -> Int -> m ()
resize DBRef{..} newSize = do
db <- readMutVar getDBRef
let !htsize = V.length $ dawgBuilderHashTable db
newHt <- case compare htsize newSize of
LT -> do
newHt <- V.grow (dawgBuilderHashTable db) (newSize - htsize)
forM_ [htsize .. pred newSize] \ix -> do
newHt <~~ fromIntegral ix $ 0
pure newHt
EQ -> pure (dawgBuilderHashTable db)
GT -> pure (V.unsafeSlice 0 newSize (dawgBuilderHashTable db))
let !ndb = db { dawgBuilderHashTable = newHt }
writeMutVar getDBRef ndb
{-# INLINE resize #-}