{-# LANGUAGE Rank2Types #-} module Distribution.Solver.Modular.RetryLog ( RetryLog , toProgress , fromProgress , mapFailure , retry , failWith , succeedWith , continueWith , tryWith ) where import Distribution.Solver.Modular.Message import Distribution.Solver.Types.Progress -- | 'Progress' as a difference list that allows efficient appends at failures. newtype RetryLog step fail done = RetryLog { unRetryLog :: forall fail2 . (fail -> Progress step fail2 done) -> Progress step fail2 done } -- | /O(1)/. Convert a 'RetryLog' to a 'Progress'. toProgress :: RetryLog step fail done -> Progress step fail done toProgress (RetryLog f) = f Fail -- | /O(N)/. Convert a 'Progress' to a 'RetryLog'. fromProgress :: Progress step fail done -> RetryLog step fail done fromProgress l = RetryLog $ \f -> go f l where go :: (fail1 -> Progress step fail2 done) -> Progress step fail1 done -> Progress step fail2 done go _ (Done d) = Done d go f (Fail failure) = f failure go f (Step m ms) = Step m (go f ms) -- | /O(1)/. Apply a function to the failure value in a log. mapFailure :: (fail1 -> fail2) -> RetryLog step fail1 done -> RetryLog step fail2 done mapFailure f l = retry l $ \failure -> RetryLog $ \g -> g (f failure) -- | /O(1)/. If the first log leads to failure, continue with the second. retry :: RetryLog step fail1 done -> (fail1 -> RetryLog step fail2 done) -> RetryLog step fail2 done retry (RetryLog f) g = RetryLog $ \extendLog -> f $ \failure -> unRetryLog (g failure) extendLog -- | /O(1)/. Create a log with one message before a failure. failWith :: step -> fail -> RetryLog step fail done failWith m failure = RetryLog $ \f -> Step m (f failure) -- | /O(1)/. Create a log with one message before a success. succeedWith :: step -> done -> RetryLog step fail done succeedWith m d = RetryLog $ const $ Step m (Done d) -- | /O(1)/. Prepend a message to a log. continueWith :: step -> RetryLog step fail done -> RetryLog step fail done continueWith m (RetryLog f) = RetryLog $ Step m . f -- | /O(1)/. Prepend the given message and 'Enter' to the log, and insert -- 'Leave' before the failure if the log fails. tryWith :: Message -> RetryLog Message fail done -> RetryLog Message fail done tryWith m f = RetryLog $ Step m . Step Enter . unRetryLog (retry f (failWith Leave))