Memoizing partially evaluated computations.

Suppose I have a list of IO computations that depends on a few very time consuming pure operations. The pure operations are not dependent on the real world:
computation :: [IO Int] computation = [ smallIOfunc timeConsumingPureOperation0 , smallIOfunc timeConsumingPureOperation1 , smallIOfunc timeConsumingPureOperation2 , smallIOfunc timeConsumingPureOperation3 ] where smallIOfunc a = print a >> return a
In my main function I would like to repeatedly print the values
main = forever $ sequence_ (map (>>=print) computation)
When I do this, all the time consuming operations will be reevaluated every run of the main loop. Is there a any (simple or smart) way to prevent the garbage collector from cleaning up the fully evaluated thunks inside my computation? As if it were something like this:
computation :: [IO Int] computation = [smallIOfunc 42, smallIOfunc 34385, smallIOfunc 3, smallIOfunc 55]
Of course I could plugin some kind of Int memoizer inside my computation, but I do not really have the control to change things `deep' inside the code. I want to have some form of snapshot of a list of partially evaluated IO computations... Any suggestions? Tanks, Sebastiaan

2009/3/18 Sebastiaan Visser
Suppose I have a list of IO computations that depends on a few very time consuming pure operations. The pure operations are not dependent on the real world:
computation :: [IO Int] computation = [ smallIOfunc timeConsumingPureOperation0 , smallIOfunc timeConsumingPureOperation1 , smallIOfunc timeConsumingPureOperation2 , smallIOfunc timeConsumingPureOperation3 ] where smallIOfunc a = print a >> return a
In my main function I would like to repeatedly print the values
main = forever $ sequence_ (map (>>=print) computation)
When I do this, all the time consuming operations will be reevaluated every run of the main loop. Is there a any (simple or smart) way to prevent the garbage collector from cleaning up the fully evaluated thunks inside my computation? As if it were something like this:
computation :: [IO Int] computation = [smallIOfunc 42, smallIOfunc 34385, smallIOfunc 3, smallIOfunc 55]
Of course I could plugin some kind of Int memoizer inside my computation, but I do not really have the control to change things `deep' inside the code. I want to have some form of snapshot of a list of partially evaluated IO computations...
Any suggestions?
Hi, If timeConsumingPureOperation is pure, the problem is thus not related to IO, and your question remains the same : how to memoize timeConsumingPureOperation for some arguments. Since you want to repeatidly call main, it seems a good idea to wrap your pure operation in a memoizing CAF (and give the wrapped version to smalIOFuncf). You can here : http://www.haskell.org/haskellwiki/Memoization HTH, Thu

On Mar 18, 2009, at 12:06 PM, minh thu wrote:
2009/3/18 Sebastiaan Visser
: Suppose I have a list of IO computations that depends on a few very time consuming pure operations. The pure operations are not dependent on the real world:
computation :: [IO Int] computation = [ smallIOfunc timeConsumingPureOperation0 , smallIOfunc timeConsumingPureOperation1 , smallIOfunc timeConsumingPureOperation2 , smallIOfunc timeConsumingPureOperation3 ] where smallIOfunc a = print a >> return a
In my main function I would like to repeatedly print the values
main = forever $ sequence_ (map (>>=print) computation)
When I do this, all the time consuming operations will be reevaluated every run of the main loop. Is there a any (simple or smart) way to prevent the garbage collector from cleaning up the fully evaluated thunks inside my computation? As if it were something like this:
computation :: [IO Int] computation = [smallIOfunc 42, smallIOfunc 34385, smallIOfunc 3, smallIOfunc 55]
Of course I could plugin some kind of Int memoizer inside my computation, but I do not really have the control to change things `deep' inside the code. I want to have some form of snapshot of a list of partially evaluated IO computations...
Any suggestions?
Hi,
If timeConsumingPureOperation is pure, the problem is thus not related to IO, and your question remains the same : how to memoize timeConsumingPureOperation for some arguments. Since you want to repeatidly call main, it seems a good idea to wrap your pure operation in a memoizing CAF (and give the wrapped version to smalIOFuncf).
The problem is that the `timeConsumingPureOperation' is somewhere very deep inside my code at a point I cannot alter. Like this:
-- This I can change: myIOCode = forever (deepLibraryCode >>= print)
-- This I cannot change: deepLibraryCode :: IO Int deepLibraryCode = makeIOfunctionFrom timeConsumingPureOperation
The separation between the make `makeIOfunctionFrom' and `timeConsumingPureOperation' might not even be that clear as in my example. That is why I am looking for some high level way of memoizing.
You can here : http://www.haskell.org/haskellwiki/Memoization
HTH, Thu

Sebastiaan Visser
Suppose I have a list of IO computations that depends on a few very time consuming pure operations. The pure operations are not dependent on the real world:
computation :: [IO Int] computation = [ smallIOfunc timeConsumingPureOperation0 , smallIOfunc timeConsumingPureOperation1 , smallIOfunc timeConsumingPureOperation2 , smallIOfunc timeConsumingPureOperation3 ] where smallIOfunc a = print a >> return a
I take it that, because you "do not really have the control to change things `deep' inside the code", it is not an option to redefine computation = [ smallIOfunc x0 , smallIOfunc x1 , smallIOfunc x2 , smallIOfunc x3 ] where smallIOfunc a = print a >> return a x0 = timeConsumingPureOperation0 x1 = timeConsumingPureOperation1 x2 = timeConsumingPureOperation2 x3 = timeConsumingPureOperation3 Can you define smallIOfunc to be more polymorphic in the monad? That is, can you define a class of monads (MonadBehavior, let's call it) that contains member functions for operations (such as print) you want to perform in smallIOfunc, then write smallIOfunc to be polymorphic over such a monad? If so, you can then implement two instances of this class: one for IO and one for a term representation of behavior. -- Edit this signature at http://www.digitas.harvard.edu/cgi-bin/ken/sig Who would have thought LISP would come back to life. Steve Bourne, in an interview about Bourne Shell.

On Wed, Mar 18, 2009 at 6:21 AM, Chung-chieh Shan
Sebastiaan Visser
wrote in article < D86A7D11-F95F-4A27-A13C-2D78AFDA2E02@cs.uu.nl> in gmane.comp.lang.haskell.cafe: Suppose I have a list of IO computations that depends on a few very time consuming pure operations. The pure operations are not dependent on the real world:
computation :: [IO Int] computation = [ smallIOfunc timeConsumingPureOperation0 , smallIOfunc timeConsumingPureOperation1 , smallIOfunc timeConsumingPureOperation2 , smallIOfunc timeConsumingPureOperation3 ] where smallIOfunc a = print a >> return a
I take it that, because you "do not really have the control to change things `deep' inside the code", it is not an option to redefine
computation = [ smallIOfunc x0 , smallIOfunc x1 , smallIOfunc x2 , smallIOfunc x3 ] where smallIOfunc a = print a >> return a x0 = timeConsumingPureOperation0 x1 = timeConsumingPureOperation1 x2 = timeConsumingPureOperation2 x3 = timeConsumingPureOperation3
Um, just to clarify, this code is exactly equivalent to the original, including sharing behavior. The only time a let (or where) clause changes sharing is if the variable is used more than once in the body. Luke

On 2009-03-18T21:24:58-0600, Luke Palmer wrote:
On Wed, Mar 18, 2009 at 6:21 AM, Chung-chieh Shan
wrote: computation = [ smallIOfunc x0 , smallIOfunc x1 , smallIOfunc x2 , smallIOfunc x3 ] where smallIOfunc a = print a >> return a x0 = timeConsumingPureOperation0 x1 = timeConsumingPureOperation1 x2 = timeConsumingPureOperation2 x3 = timeConsumingPureOperation3
Um, just to clarify, this code is exactly equivalent to the original, including sharing behavior. The only time a let (or where) clause changes sharing is if the variable is used more than once in the body.
Ah, good point! Of course, "timeConsumingPureOperation0" above is a metavariable for a Haskell expression, not just a Haskell variable. But I guess I also took "smallIOfunc" to be a metavariable for a Haskell context (i.e., a Haskell expression with a hole), not just a Haskell function name. You make an important point that sharing is changed only if the variable (such as x0) is used more than once in the body. Let me note that the definition of "computation" doesn't have to mention "x0" multiple times syntactically for x0 to be used more than once. It's enough for "x0" to appear once under a lambda. Here's a concrete example: main :: IO () main = once >> once once :: IO () once = do putStrLn "foo" putStrLn (unsafePerformIO (putStrLn "hello") `seq` "world") If I put "() <-" in front of the second-to-last line, then "hello" appears twice, not once, in the output. -- Edit this signature at http://www.digitas.harvard.edu/cgi-bin/ken/sig 100 Days to close Guantanamo and end torture http://100dayscampaign.org/ http://www.avaaz.org/en/end_the_war_on_terror/

On Wed, Mar 18, 2009 at 10:37 PM, Chung-chieh Shan
You make an important point that sharing is changed only if the variable (such as x0) is used more than once in the body. Let me note that the definition of "computation" doesn't have to mention "x0" multiple times syntactically for x0 to be used more than once. It's enough for "x0" to appear once under a lambda. Here's a concrete example:
main :: IO () main = once >> once
once :: IO () once = do putStrLn "foo" putStrLn (unsafePerformIO (putStrLn "hello") `seq` "world")
If I put "() <-" in front of the second-to-last line, then "hello" appears twice, not once, in the output.
You're right. Of course, now you're in the compiler's territory. If you compile the () <- version with optimizations, the unsafePerformIO is lifted out and "hello" is again only printed once. So yes, to ensure sharing under a lambda, it's safest to use an explicit let/where clause. Luke
participants (4)
-
Chung-chieh Shan
-
Luke Palmer
-
minh thu
-
Sebastiaan Visser