Safe Haskell | None |
---|---|
Language | Haskell2010 |
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.
- data TimeInterval
- time :: TimeUnit t => t -> TimeInterval
- microseconds :: TimeInterval -> Integer
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.