I want to write a function that takes a time limit (in seconds) and a list, and computes as many elements of the list as possible within the time limit.

My first attempt was to first write the following function, which times a pure computation and returns the time elapsed along with the result:

import Control.DeepSeq
import System.CPUTime

type Time = Double

timed :: (NFData a) => a -> IO (a, Time)
timed x = do t1 <- getCPUTime
             r  <- return $!! x
             t2 <- getCPUTime
             let diff = fromIntegral (t2 - t1) / 10^12
             return (r, diff)

I can then define the function I want in terms of this:

timeLimited :: (NFData a) => Time -> [a] -> IO [a]
timeLimited remaining []     = return []
timeLimited remaining (x:xs) = if remaining < 0
    then return []
    else do
        (y,t) <- timed x
        ys    <- timeLimited (remaining - t) xs
        return (y:ys)

This isn't quite right though. Even ignoring timing errors and floating point errors, this approach never stops the computation of an element of the list once it has started, which means that it can (and in fact, normally will) overrun its time limit.

If instead I had a function that could short-circuit evaluation if it had taken too long:

timeOut :: Time -> a -> IO (Maybe (a,t))
timeOut = undefined

then I could write the function that I really want:

timeLimited' :: Time -> [a] -> IO [a]
timeLimited' remaining []     = return []
timeLimited' remaining (x:xs) = do
    result <- timeOut remaining x
    case result of
        Nothing    -> return []
        Just (y,t) -> do
            ys <- timeLimited' (remaining - t) xs
            return (y:ys)

My questions are:

  1. How do I write timeOut?
  2. Is there a better way to write the function
Edit
Report