In a Haskell system I don't have much control over, I provide a syntactically pure function with the following signature:
doTheWork :: Int -> TheInput -> MyResult
doTheWork counter data = ...
This function does some work and returns its result. However the function is called again and again (until the system resource are used up) with incremented Int
s to do more work on the same problem (the same TheInput
data).
So to make use of the fact to be called again and again and not every time start again, I would need to store the intermediate MyResults
. Of course, the "proper" semantically pure way would be that the system calls the function together with the old last results; i.e. the function signature would be something like doTheWork :: MyResult -> Int -> TheInput -> MyResult
, but I have no control over the signature. The signature my function has to conform to is the shown at the beginning of the question! And I cannot move the function into the IO Monad of the program (because I don't have control over where the function is called).
So I was thinking to store the MyResults
myself in memory via unsafePerformIO
or similar. However I got stuck on the "safe to memory part". I can print the results out to stdout with something like
storeMyResults :: MyResults -> MyResults
storeMyResults data =
unsafePerformIO $ do
putStrLn show data)
return data
and then use storeMyResults data
at the end of doTheWork
when returning the results to write them out each time the function has computed new results.
But I have no idea how to write the MyResults
value to memory and then read it out the next time I get called. I think I could write it to a file, but that's unnecessarily slow.
Is there a way to store a value in a memory location?
The typical way to do this is to unsafePerformIO
yourself a top-level IORef
. IORef
s normally provide "mutable memory location" functionality within IO
, but you can also break one out of IO
.
data MemoRecord = MemoRecord Int TheInput MyResult
cacheCell :: IORef (Maybe MemoRecord)
cacheCell = unsafePerformIO (newIORef Nothing)
At this point, you have a mutable, program-global variable, like you would in C.
Proceed to wire this into doTheWork
. Note to take care to save the inputs as well as the output, so you can check you're doing the right thing. Again, do this in IO
and then unsafePerformIO
yourself out.
-- really, it's up to you how you want to do this
-- i'll just write something plausible so you know how
doTheWork :: Int -> TheInput -> MyResult
doTheWork i x = unsafePerformIO $ do
r <- doTheWorkHinted i x <$> (check =<<) <$> readIORef cacheCell
writeIORef cacheCell (Just (MemoRecord i x r))
return r
where
-- check :: MemoRecord -> Maybe (Int, MyResult)
check (MemoRecord i x' r)
| x == x' = Just (i, r) -- assuming Eq TheInput; otherwise you may have to resort to System.Mem.StableName, which can give false negatives
| otherwise = Nothing
-- given an int, the input, and maybe a previous result from the same input, find the result
doTheWorkHinted :: Int -> TheInput -> Maybe (Int, MyResult) -> MyResult
doTheWorkHinted = error "implement this"