tagged-binary-0.1.0.0: Data/Binary/Tagged/Internal.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
-- |
-- Module : Data.Binary.Tagged.Internal
-- Copyright : (c) Justin Le 2014
-- License : MIT
--
-- Maintainer : justin@jle.im
-- Stability : stable
-- Portability : portable
--
-- Internals for the library, exported in case you should need it.
-- Usually, the parts you would need should be re-exported in
-- 'Data.Binary.Tagged'.
--
module Data.Binary.Tagged.Internal (
-- * Data types
Tagged -- abstract, instances: Show, Eq, Binary, Typeable, Generic
, TagFingerprint -- abstract, instances: Show, Eq, Ord, Binary, Typeable, Generic, Default
-- * Tagging and extracting data
, tag -- :: Typeable a => a -> Tagged a
, getTagged -- :: Typeable a => Tagged a -> Maybe a
, tagMatched -- :: Typeable a => Tagged a -> Bool
-- * 'TagFingerprint' utilities
, typeFingerprint -- :: Typeable a => a -> TagFingerprint
, tagFingerprint -- :: Tagged a -> TagFingerprint
) where
import Data.Binary
import Data.Default
import Data.ByteString.Lazy
import Data.ByteString.Lazy.Char8 as LC
import Data.Maybe (isJust)
import Data.Typeable.Internal
import GHC.Generics
-- | A data type tupling together data with a 'TagFingerprint',
-- representing data tagged with its type. You really should never have to
-- use this type; it's best to interface directly with data using
-- 'encodeTagged', 'decodeTagged', etc. Use 'tag' to tag data and
-- 'extractTagged' to extract data from valid tagged data.
data Tagged a = Tagged !TagFingerprint a
deriving (Show, Eq, Generic, Typeable)
-- | A data type representing a fingerprint for a 'Typeable' type.
-- Ideally, this would be 'Data.Typeable.Internal''s own 'Fingerprint'
-- types; however, for some reason, the fingerprints for the same data type
-- from the same modules differ between different GHC backends. So for
-- now, it is just a 'ByteString' representation of the name of the type.
-- This is literally a bad idea, and so two types with the same name but
-- from different modules will share a non-unique 'TagFingerprint'.
-- Hopefully in the future when I find out a way to fix this or the GHC
-- backend maintainers find a way to provide consistent type fingerprints,
-- this will be fixed.
--
-- This type is mostly used for the ability to categorized Tagged items
-- by their type.
--
-- There is a 'Default' instance, because the constructor is hidden. For
-- now, it is just an empty 'ByteString', but when fingerprinting works for
-- real, think of it as a way to generate a fingerprint that will most
-- likely not be matched by any type, in case the need ever comes up.
newtype TagFingerprint = TagFP ByteString
deriving (Show, Typeable, Generic, Eq, Ord)
instance Binary a => Binary (Tagged a)
instance Binary TagFingerprint
instance Default TagFingerprint where
def = TagFP empty
-- | Wrap data inside a 'Tagged' tuple.
tag :: Typeable a => a -> Tagged a
tag x = Tagged (typeFingerprint x) x
-- | Compute the 'Fingerprint' representing a type. It is non-strict on
-- its parameter, so passing in undefined should work if you want to just
-- get the 'Fingerprint' of a specific type without having data of that
-- type on hand:
--
-- > typeFingerprint (undefined :: Int)
--
typeFingerprint :: Typeable a => a -> TagFingerprint
typeFingerprint = TagFP . LC.pack . show . typeOf
-- | Extract data out of a 'Tagged', but only the type of the data matches
-- the type represented by the fingerprint. It is polymorphic on its
-- output and meant to be used when decoding a 'Tagged' item with a desired
-- type.
getTagged :: Typeable a => Tagged a -> Maybe a
getTagged (Tagged tfp x) | tfp == xfp = Just x
| otherwise = Nothing
where
xfp = typeFingerprint x
-- | Check if the type inside the 'Tagged' matches the fingerprint.
tagMatched :: Typeable a => Tagged a -> Bool
tagMatched = isJust . getTagged
-- | Extract the 'Fingerprint' out of a 'Tagged'. Mostly used so that you
-- can categorize and associate Tagged items; to check if a 'Tagged' is
-- of a desired typed, 'getTagged' and 'tagMatched' might be more useful.
tagFingerprint :: Tagged a -> TagFingerprint
tagFingerprint (Tagged fp _) = fp