8
import Math.NumberTheory.Primes (factorise)
import System.Timeout (timeout)
import Control.Monad (liftM)

type RetType = [(Integer, Int)] -- factorise's return type

-- proposed function
timedFact :: Integer -> Integer -> Either RetType Integer
timedFact u n = ?

Trying to understand how to write a wrapper function for factorise which times out after u usec. If it succeeds it returns RetType otherwise it returns Integer (what was passed in)

I'm kind of new to Haskell. I understand a timeout requires working in the IO Monad but I'm having trouble pulling back the appropriate result. (Note: I'm not married to Either. Maybe RetType would be fine, too).

Thanks for any help

Will Ness
  • 70,110
  • 9
  • 98
  • 181

1 Answers1

6

Looking at the type, timeout :: Int -> IO a -> IO (Maybe a), it could be used as

import Math.NumberTheory.Primes (factorise)
import System.Timeout (timeout)
import Control.Exception (evaluate)
import Control.DeepSeq (force)

timedFact :: Int -> Integer -> IO (Maybe [(Integer, Int)])
timedFact u = 
      timeout u . evaluate . force . factorise 

Testing:

 #> timedFact 3000000 1231231231223234234273434343469494949494499437141
Nothing
(3.04 secs, 2639142736 bytes)

 #> timedFact 4000000 1231231231223234234273434343469494949494499437141
Just [(1009,1),(47729236307,1),(125199345589541,1),(204202903382078984027,1)]
(3.07 secs, 2662489296 bytes)

update: as user2407038 says in the comments (thanks!),

timedFact u n = timeout u (return $!! factorise n)

also works. ($!!) comes from Control.DeepSeq too. To quote the docs, "In the expression f $!! x, x is fully evaluated before the function f is applied to it".

Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 1
    I think you'd have to force the evaluation of `x`, otherwise the evaluation happens outside of `timeout`. – bheklilr Oct 01 '15 at 02:07
  • `force` evaluates to normal form. Do you really need `evaluate` after it? – user2407038 Oct 01 '15 at 02:37
  • @user2407038 I tried putting `return` there but it didn't bail out. – Will Ness Oct 01 '15 at 02:38
  • 1
    @user2407038 cf. https://mail.haskell.org/pipermail/beginners/2010-April/004023.html – Will Ness Oct 01 '15 at 02:46
  • 5
    @user2407038, while `evaluate x` looks a lot like `return $! x`, it turns out that there are important but subtle differences. Subtle enough to have confused some of the lead GHC implementers; important enough to have led to a special `seq#` primop just to define `evaluate` properly. – dfeuer Oct 01 '15 at 03:44
  • 2
    @WillNess @dfeuer `return $!! factorise v` works. `\u v -> timeout u $ return $ let r = factorise v in r \`deepseq\` r` doesn't work and is equivalent to `force (return r)`. However, `\u v -> timeout u $ let r = factorise v in r \`deepseq\` return r` does work and is equivalent to `return $!! r`. The `evaluate` is truly unnecessary but since the value is already evaluated when evaluate is called, I doubt it has any overhead, so it probably doesn't matter. – user2407038 Oct 01 '15 at 04:37
  • Please ignore the above comment it was incomplete . Here's the full one: Thanks, Will. This is about as far as I got actually doing something slightly different. I am interested in pulling the underlying result back from IO as well. If I write in ghci: let x = timedFact 4000000 1231231231223234234273434343469494949494499437141 :t x -- gives x :: IO (Maybe [(Integer, Int)]) How would I pull back the underlying value? I tried readIORef in a do clause but that didn't work for me. Thanks for your help, folks – user3424410 Oct 01 '15 at 11:08
  • Ah, unsafePerformIO (gulp) is what I'm looking for. Do you think if I had several of these "timedFacts" running in parallel that there's a chance for corrupion, etc? – user3424410 Oct 01 '15 at 12:11
  • you pull the values in a do block, like this: `do { x <- timedFact 40 40; case x of Nothing -> ... ; Just factorization -> ... use factorization ... }`. – Will Ness Oct 01 '15 at 12:17
  • 2
    @user3424410 1) You know that you can delete comments? 2) I strongly advise against `unsafePerformIO`. There *is* a reason why it has `unsafe` in its name. Whenever you use it you should provide a proof that it wont cause troubles. If you aren't able to do that you should simply avoid using it because you can mess things up pretty easily. Simply use `timedFact` as any other `IO` action. – Bakuriu Oct 01 '15 at 12:21
  • Thanks, deleted. Thinking about this some more. The better way to go about this is to slightly extend the library to be able to configure the number of elliptical curves attempted. – user3424410 Oct 01 '15 at 16:22
  • 1
    @user3424410 This is a terrible place to use `unsafePerformIO`: there is no reason to believe that running a pure computation twice will take the same amount of time (hence either hit or miss the timeout) both times. There's tons of variables involved, including: which thunks have been forced so far; which reads hit in the cache and which don't; how your computation/watchdog threads interact with the OS' scheduler; and many others. – Daniel Wagner Oct 01 '15 at 20:31
  • @DanielWagner: Of course, there's no reason to believe that and I didn't give any indication I would expect that. Besides, I already responded about using a different (wiser) tack. There's a practical need (which as I said I can replace by modifying the underlying library) The initial thinking was that if I had a large number I needed to factor, I could specify an upper bound on the time, be it a day or 5 minutes, to factor it. Any unfactored piece could be marked composite. Anyways, that was the original thinking. Thanks everyone for setting me on the right track. – user3424410 Oct 01 '15 at 22:12