{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} #if !MIN_VERSION_base(4,8,0) {-# LANGUAGE DeriveDataTypeable #-} #endif -- | -- Provides a data type similar to 'Data.Dynamic.Dynamic' -- from @base:Data.Dynamic@, but with a 'Binary' instance. -- This of course means that only instances of @Binary@ can -- be encapsulated in this @Dynamic@. module Data.Dynamic.Binary ( -- re-exported for convenience module RT -- * The @Dynamic@ type , Dynamic -- * Converting to and from @Dynamic@ , toDyn , fromDyn , fromDynamic -- * Accessor for the contained @TypeRep@ , dynTypeRep ) where import qualified Data.Rank1Typeable as RT import qualified Data.ByteString.Lazy as BSL import GHC.Exception ( Exception ) import Data.IORef ( IORef, newIORef, readIORef, writeIORef ) import Data.Binary ( Binary(..), encode, decode ) import Data.Typeable ( Typeable ) import System.IO.Unsafe ( unsafePerformIO ) import Unsafe.Coerce ( unsafeCoerce ) -- | -- This @Dynamic@ is a variant of the 'Data.Dynamic.Dynamic' -- from @base:Data.Dynamic@ with a 'Binary' instance. -- It encapsulates a value of an arbitrary -- type, provided that the type is instance of both -- @Typeable@ and @Binary@. -- -- The advantage over just using a 'ByteString' is the type safety: -- Raw @ByteString@s have no associated type, and the @Binary@ -- interface makes no guarantee that the representations for -- values of different types are different. -- -- The advantage over using a type-tagged @ByteString@ is that -- @Dynamic@ avoids unnecessary encoding/decoding by internally -- containing either a @ByteString@ or a decoded value. data Dynamic = Dynamic RT.TypeRep -- the reason why we cannot simply use a `forall a . a` here -- is that when decoding, we have no access to the Binary -- instance, so the decoding needs to be postponed until the -- user provides an instance when calling fromDyn(amic). (IORef (Either BSL.ByteString LoadedContent)) #if !MIN_VERSION_base(4,8,0) deriving ( Typeable ) #endif data LoadedContent = forall a . LoadedContent a (a -> BSL.ByteString) instance Show Dynamic where -- the instance just prints the type representation. showsPrec _ (Dynamic tRep _) = showString "<<" . showsPrec 0 tRep . showString ">>" -- here so that it isn't an orphan: instance Exception Dynamic instance Binary Dynamic where put (Dynamic tRep vRef) = do put tRep put $ unsafePerformIO $ flip fmap (readIORef vRef) $ \eith -> case eith of Left bs -> bs Right (LoadedContent v enc) -> enc v get = do tRep <- get bs <- get return $ Dynamic tRep $ unsafePerformIO $ newIORef $ Left $ bs -- | -- Converts an arbitrary value into an object of type 'Dynamic'. toDyn :: (Typeable a, Binary a) => a -> Dynamic toDyn v = Dynamic (RT.typeOf v) e where e = unsafePerformIO $ newIORef $ Right $ LoadedContent v encode -- | -- Converts a 'Dynamic' object back into an ordinary Haskell value of -- the correct type. fromDyn :: (Typeable a, Binary a) => Dynamic -> a -- ^ default value -> a -- ^ if types match, the value contained in the @Dynamic@, -- otherwise the default value. fromDyn (Dynamic tRep vRef) def | RT.typeOf def == tRep = readFromIORef vRef | otherwise = def -- | -- Converts a 'Dynamic' object back into an ordinary Haskell value of -- the correct type. fromDynamic :: forall a . (Typeable a, Binary a) => Dynamic -> Maybe a -- ^ if types match, @Just@ the contained value, -- otherwise @Nothing@. fromDynamic (Dynamic tRep vRef) = if tRep == RT.typeOf (undefined::a) then Just $ readFromIORef vRef else Nothing readFromIORef :: forall a . Binary a => IORef (Either BSL.ByteString LoadedContent) -> a readFromIORef vRef = unsafePerformIO $ do eith <- readIORef vRef case eith of Left bs -> do let v = decode bs :: a -- this type signature is necessary -- solely to make ghci work for this -- module. probably some bug? writeIORef vRef (Right $ LoadedContent v encode) return $ v Right (LoadedContent v _) -> return $ unsafeCoerce v -- | -- Getter for the 'TypeRep' of this @Dynamic@. dynTypeRep :: Dynamic -> RT.TypeRep dynTypeRep (Dynamic tRep _) = tRep -- _test :: IO () -- _test = do -- let v1 = toDyn "hello, world" -- v2 = toDyn v1 -- print v1 -- print v2 -- let b1 = encode v1 -- b2 = encode v2 -- print b1 -- print b2 -- let c1 :: Dynamic -- c1 = decode b1 -- c2 :: Dynamic -- c2 = decode b2 -- print c1 -- print c2 -- let v3 :: String -- v3 = fromDyn c1 "problem" -- v4 :: Dynamic -- v4 = fromDyn c2 (toDyn "problem") -- v5 :: String -- v5 = fromDyn v4 "problem" -- v6 :: String -- v6 = fromDyn v4 "problem" -- print v3 -- print v4 -- print v5 -- print v6