Я хочу написать функцию, которая занимает лимит времени (в секундах) и список, и вычисляет как можно больше элементов списка в течение срока.
Моя первая попытка состояла в том, чтобы сначала написать следующую функцию, которая умножает время на чистое вычисление и возвращает время, прошедшее с результатом:
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)
Затем я могу определить функцию, которую я хочу в терминах:
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)
Это не совсем правильно. Даже игнорируя ошибки синхронизации и ошибки с плавающей запятой, этот подход никогда не останавливает вычисление элемента списка после его запуска, а это означает, что он может (и фактически нормально) превышать его временные ограничения.
Если бы вместо этого у меня была функция, которая могла бы провести короткую проверку, если бы она заняла слишком много времени:
timeOut :: Time -> a -> IO (Maybe (a,t))
timeOut = undefined
тогда я мог бы написать нужную мне функцию:
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)
Мои вопросы:
- Как написать
timeOut
? - Есть ли лучший способ написать функцию
timeLimited
, например, такую, которая не страдает от накопленной ошибки с плавающей запятой из суммирования разностей во времени несколько раз?