{- This file is part of time-interval. - - Written in 2015 by fr33domlover . - - ♡ Copying is an act of love. Please copy, reuse and share. - - The author(s) have dedicated all copyright and related and neighboring - rights to this software to the public domain worldwide. This software is - distributed without any warranty. - - You should have received a copy of the CC0 Public Domain Dedication along - with this software. If not, see - . -} -- | 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. module Data.Time.Interval ( TimeInterval () , time , microseconds ) where import Data.Time.Units (TimeUnit (..)) -- | A time duration. newtype TimeInterval = TimeInterval Integer deriving (Eq, Ord, Show) -- | Convert a time value expressed in a some time unit into a 'TimeInterval'. time :: TimeUnit t => t -> TimeInterval time = TimeInterval . toMicroseconds -- | Express a 'TimeInterval' in microseconds. microseconds :: TimeInterval -> Integer microseconds (TimeInterval i) = i