Hi, I am trying to write type-safe interface to a key-value store (gdbm), C
interface of which can be simplified to following:
data DB
openRead :: FilePath -> IO DB
openWrite :: FilePath -> IO DB
close :: DB -> IO ()
fetch :: DB -> ByteString -> IO (Maybe ByteString)
store :: DB -> ByteString -> ByteString -> IO ()
firstkey :: DB -> IO (Maybe ByteString)
nextkey :: DB -> ByteString -> IO (Maybe ByteString)
And there are following rules about this interface:
-
store
can only be called on database open withopenWrite
. - You should call
close
as soon you don’t need database anymore. - You may not use
DB
object once you closed it. - Functions
firstkey
andnextkey
allow you to iterate keys, but you may
not callstore
between call offirstkey
andnextkey
. - User not necessary want to iterate all keys.
- User may want to work with multiple databases.
Here is my attempt to tackle the problem. I used
regions library to address
second and third points, and phantom parameter to track if database is in write
mode (ignore Either ByteString
part, it is about error reporting). You can
take a look at compilable source here
openRead :: (RegionIOControl pr, r ~ RegionT s pr) => FilePath -> r (Either ByteString (GDBM ReadMode r))
openWrite :: (RegionIOControl pr, r ~ RegionT s pr) => FilePath -> r (Either ByteString (GDBM WriteMode r))
fetch :: (MonadIO cr, AncestorRegion pr cr) => GDBM m pr -> ByteString -> cr (Maybe ByteString)
store :: (MonadIO cr, AncestorRegion pr cr) => GDBM WriteMode pr -> ByteString -> ByteString -> cr ()
And now I need somehow to do key iteration. I imagine I could have type like
following:
iterate :: (Monoid m, MonadIO cr, AncestorRegion pr cr) => GDBM m pr -> (m -> ByteString -> cr (m, Bool)) -> cr ()
iterate db f = do ...
where Bool
value designates if user wants to continue iterating or want to
break. Problem is that there is nothing stopping user from using db
argument
inside f
to call store
, at which point we lose. Effectively, I need to
temporary revoke access to database handle from outer scope, and I am not aware
of any mechanisms for doing so.
Am I holding it wrong?