packages feed

halfs-2.0: Halfs/Classes.hs

{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving,
             FunctionalDependencies, FlexibleContexts,
             FlexibleInstances, ScopedTypeVariables, BangPatterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Halfs.Classes
  ( HalfsCapable
  , Lockable(..)
  , Reffable(..)
  , TimedT(..)
  , Timed(..)
  , Bitmapped(..)
  , Threaded(..)
  , IOLock
  )
 where

import Control.Applicative
import Control.Concurrent (ThreadId, myThreadId)
import Control.Concurrent.MVar
import Control.Exception
import Control.Monad.ST
import Data.Array.IO
import Data.Array.ST
import Data.IORef
import Data.Ratio            (numerator)
import Data.Serialize
import Data.STRef
import Data.Time.Clock
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
import Data.Time.LocalTime   () -- for Show UTCTime instance
import Data.Word

import Foreign.C.Types       (CTime)
import GHC.Int               (Int32)

-- ----------------------------------------------------------------------------

-- Any monad used in Halfs must implement the following interface:
class ( Bitmapped b m
      , Timed t m
      , Reffable r m
      , Lockable l m
      , Serialize t
      , Threaded m
      , Functor m
      , Monad m
      , Show t -- For debugging
      ) =>
  HalfsCapable b t r l m | m -> b t r l

instance HalfsCapable (IOUArray Word64 Bool)   UTCTime IORef     IOLock IO
instance HalfsCapable (STUArray s Word64 Bool) Word64  (STRef s) ()     (ST s)

-- ----------------------------------------------------------------------------

-- |A monad implementing Timed implements a monotonic clock that can be read
-- from. One obvious implementation is using the system clock. Another might be
-- a step counter.
class (Monad m, Eq t, Ord t) => Timed t m | m -> t where
  getTime   :: m t
  toCTime   :: t -> m CTime
  fromCTime :: CTime -> m t

-- |This is a monad transformer for the Timed monad, which will work for 2^64
-- steps of an arbitrary underlying monad.
newtype TimedT m a = TimedT { runTimerT :: Word64 -> m a }

ttGetTime :: Monad m => TimedT m Word64
ttGetTime = TimedT $ \ t -> return t

instance Monad m => Monad (TimedT m) where
  return a = TimedT $ \ _ -> return a
  m >>= k  = TimedT $ \ t -> do
               a <- runTimerT m t
               runTimerT (k a) (t + 1)

instance Serialize UTCTime where
  put x = do
    putWord64be $ fromIntegral $ fromEnum $ utctDay x
    putWord64be $
      -- We have no way to extract the underlying fixed-precision Integer from
      -- the DiffTime, but picosecond resolution for DiffTime is documented, so
      -- we scale via conversion to Rational (i.e., we reconstruct the
      -- underlying fixed-precision Integer).  The assert is simply in case the
      -- underlying representation changes at some point in the future.
      let dt2pico = numerator . (1000000000000*) . toRational
          off     = fromIntegral $ dt2pico $ utctDayTime x
      in assert (off >= (minBound :: Word64) && off <= (maxBound :: Word64)) off

  get = do
    UTCTime
    <$> (toEnum . fromIntegral)                `fmap` getWord64be
    <*> (picosecondsToDiffTime . fromIntegral) `fmap` getWord64be

instance Timed UTCTime IO where
  getTime = getCurrentTime >>= toCTime >>= fromCTime
    -- Pass the current time through conversion to/fromCTime so that all times
    -- that originate here have CTime granularity.  We do this so that our
    -- internally-acquired times have the same granularity as those coming in
    -- from outside via the hFuse binding (e.g., via the setFileTimes function).
  toCTime t =
    -- We'll be converting UTCTimes to POSIXTimes to CTime values, which have
    -- implementation-specific size.  I don't think it's safe to truncate based
    -- on the size reported by the Storable instance for CTime, as we'd still
    -- have to make assumptions about the underlying rep (e.g., it's not a real,
    -- etc.).  As a keep-it-simple concession, we'll err on the side of caution
    -- and assume that we have a 32 bit signed int representation, and clamp
    -- values based on that.  This should be okay until early 2038 :).
    let ub = fromIntegral (maxBound :: Int32)
        i :: Integer = truncate $ utcTimeToPOSIXSeconds t
    in return ((fromIntegral $ if i >= ub then ub else i) :: CTime)
  fromCTime = return . posixSecondsToUTCTime . realToFrac

instance Timed Word64 (ST s) where
  getTime   = undefined
  toCTime   = undefined
  fromCTime = undefined

instance Monad m => Timed Word64 (TimedT m) where
  getTime   = ttGetTime
  toCTime   = undefined
  fromCTime = undefined

-- ---------------------------------------------------------------------------

-- |A monad implementing Reffable implements a reference type that allows for
-- mutable state.
class Monad m => Reffable r m | m -> r where
  newRef    :: a -> m (r a)
  readRef   :: r a -> m a
  writeRef  :: r a -> a -> m ()
  modifyRef :: r a -> (a -> a) -> m ()
  modifyRef r f = readRef r >>= writeRef r . f

instance Reffable (STRef s) (ST s) where
  newRef   = newSTRef
  readRef  = ($!) readSTRef
  writeRef = ($!) writeSTRef

instance Reffable IORef IO where
  newRef         = newIORef
  readRef        = ($!) readIORef
  writeRef !r !v = writeIORef r v

-- ---------------------------------------------------------------------------

-- | A monad implementing Threaded can obtain its thread id
class Monad m => Threaded m where
  getThreadId :: m ThreadId

instance Threaded IO where
  getThreadId = myThreadId

instance Threaded (ST s) where
  getThreadId = undefined

-- ---------------------------------------------------------------------------

-- |A monad implementing locks.
class Monad m => Lockable l m | m -> l where
  newLock  :: m l
  lock     :: l -> m ()
  release  :: l -> m ()

instance Lockable () (ST s) where
  newLock   = return ()
  lock _    = return ()
  release _ = return ()

newtype IOLock = IOLock (MVar ())

instance Lockable IOLock IO where
  newLock            = IOLock `fmap` newMVar ()
  lock (IOLock l)    = takeMVar l
  release (IOLock l) = putMVar l ()

-- ---------------------------------------------------------------------------

-- | A monad implementing a bitmap
class Monad m => Bitmapped b m | m -> b where
  newBitmap :: Word64 -> Bool -> m b
  clearBit  :: b -> Word64 -> m ()
  setBit    :: b -> Word64 -> m ()
  checkBit  :: b -> Word64 -> m Bool
  toList    :: b -> m [Bool]

instance Bitmapped (IOUArray Word64 Bool) IO where
  newBitmap s e = newArray (0, s - 1) e
  clearBit b i  = writeArray b i False
  setBit b i    = writeArray b i True
  checkBit      = readArray
  toList        = getElems

instance Bitmapped (STUArray s Word64 Bool) (ST s) where
  newBitmap s e = newArray (0, s - 1) e
  clearBit b i  = writeArray b i False
  setBit b i    = writeArray b i True
  checkBit      = readArray
  toList        = getElems