time-interval-0.1.0.0: Use a time unit class, but hold a concrete time type.

Safe HaskellNone
LanguageHaskell2010

Data.Time.Interval

Description

Suppose you have a program which periodically reloads state and saves some logs, and you'd like the intervals for these periodic actions to be expressed using time units, and abstract away the internal representation until the site of actual use. Your code may look like this:

data AppState tr ts = AppState
    { userName       :: String
    , newMessages    :: [String]
    , reloadInterval :: tr
    , saveInterval   :: ts
    }

type App tr ts = StateT (AppState tr ts) IO

setReloadInterval :: TimeUnit t => t -> App ()
setReloadInterval new = modify $ \ s -> s { reloadInterval = new }

syncStateWithFiles :: (TimeUnit tr, TimeUnit ts) => App tr ts ()
syncStateWithFiles = do
    reloadMicrosec <- liftM toMicroseconds $ gets reloadInterval
    saveMicrosec <- liftM toMicroseconds $ gets saveInterval
    {- ... use the values ... -}

And usage looks like this:

setReloadInterval (5 :: Second)

So every time you add, change or remove a time field, all your type signatures need to be updated and all your App actions, even unrelated to those times, are stuck with time type parameters in their type signatures.

An easy and common approach to avoid all the mess is to store the time as an integer, i.e. applying toMicroseconds when setting the value rather than when using it. That makes things much easier, but then your type (and your API) expose the time directly as a number of microseconds, and people end up writing things like 1000 * 1000 * 60 * 5 to say "5 minuts". It's also an internal technical detail there's no reason to expose, and should be possible to change without breaking anything - e.g. what if your scheduling tool one day moves to a higher precision than microseconds? Your high-level API would ideally not let that change float all the way up.

Here's how things can work when using this library.

data AppState = AppState
    { userName       :: String
    , newMessages    :: [String]
    , reloadInterval :: TimeInterval
    , saveInterval   :: TimeInterval
    }

type App = StateT AppState IO

setReloadInterval :: TimeUnit t => t -> App ()
setReloadInterval new = modify $ \ s -> s { reloadInterval = time new }

syncStateWithFiles :: App ()
syncStateWithFiles = do
    reloadMicrosec <- liftM microseconds $ gets reloadInterval
    saveMicrosec <- liftM microseconds $ gets saveInterval
    {- ... use the values ... -}

And usage looks the same:

setReloadInterval (5 :: Second)

Also, even if you let the user use the time function in their code, e.g. like this:

setReloadInterval $ time (5 :: Second)

... you stil get the advantages from both worlds.

Synopsis

Documentation

time :: TimeUnit t => t -> TimeInterval Source

Convert a time value expressed in a some time unit into a TimeInterval.

microseconds :: TimeInterval -> Integer Source

Express a TimeInterval in microseconds.