[Haskell-cafe] Troubles understanding memoization in SOE
Paul Hudak
paul.hudak at yale.edu
Mon Sep 24 20:34:57 EDT 2007
Hi Peter. Paul Liu replied well to your later email, but I just wanted
to point out that memoization is not being used here to make the
recursion work -- lazy evaluation does just fine. Rather, the
memoization is being used for what it's normally good for, namely, to
avoid repeated computation. In a recursive context having multiple
references to the recursive variable, this can result in an exponential
blow-up that grinds the computation to a halt very quickly. I suspect
that when you observed your program getting "stuck" that it was simply
blowing up so quickly that it /appeared /stuck.
Also, the reason that there is no space leak in the memoization process
is that, as Paul Liu pointed out, I only save the last value -- that's
the reason for the IORef. The last value is sufficient because FAL is
carefully designed so that it executes each time step completely before
the next one begins.
Finally, I should point out this is the only place in SOE where I use
unsafe features in Haskell. I felt so bad about it that you'll notice
that I don't even discuss it in the text! Interestingly, also as Paul
Liu pointed out, switching to arrows solves the problem, but in a subtle
way that we only recently realized. The paper that Paul cited
(http://www.cs.yale.edu/~hl293/download/leak.pdf) describes this in detail.
I hope this helps,
-Paul Hudak
Peter Verswyvelen wrote:
> Hi,
>
> in SOE, the following memoization function is implemented:
> memo1 :: (a->b) -> (a->b)
> memo1 f = unsafePerformIO $ do
> cache <- newIORef []
> return $ \x -> unsafePerformIO $ do
> vals <- readIORef cache
> case x `inCache` vals of
> Nothing -> do let y = f x
> writeIORef cache [(x,y)] -- ((x,y) :
> -- if null vals then [] else [head vals])
> return y
> Just y -> do return y
>
> inCache :: a -> [(a,b)] -> Maybe b
> x `inCache` [] = Nothing
> x `inCache` ((x',y'):xys) =
> if unsafePtrEq x x' then Just y' else x `inCache` xys
>
> This is then used in
>
> type Time = Float
> type UserAction = G.Event
>
> data G.Event
> = Key Char Bool
> | Button Point Bool Bool
> | MouseMove Point
> | Resize
> | Closed
> deriving Show
>
> newtype Behavior a = Behavior (([Maybe UserAction],[Time]) -> [a])
> newtype Event a = Event (([Maybe UserAction],[Time]) -> [Maybe a])
>
> Behavior fb `untilB` Event fe =
> memoB $ Behavior (\uts@(us,ts) -> loop us ts (fe uts) (fb uts))
> where loop (_:us) (_:ts) ~(e:es) (b:bs) =
> b : case e of
> Nothing -> loop us ts es bs
> Just (Behavior fb') -> fb' (us,ts)
>
> memoB :: Behavior a -> Behavior a
> memoB (Behavior fb) = Behavior (memo1 fb)
>
>
> If I understand it correctly, the memoization is required because
> otherwise recursive "streams" wouldn't work. For example, in the Pong
> game example, a ballPositionX stream is generated by integrating a
> ballVelocityX stream, but the ballVelocityX stream changes sign when
> the ball hits the left or right walls, and to determine that event,
> the ballPositionX stream is required. So both streams are mutually
> recursive, and without memoization, the program would be stuck (at
> least my own FRP experiments, which don't use memoization yet, gets
> stuck :-)). Another trick to prevent this, is the "b : case e of" code
> in untilB, which causes the event to be handled a bit too late, to
> avoid cyclic interdependencies.
>
> I hope I got that right. Now my questions.
>
> So, the keys (x) and values (y) in (memo1 fb) are streams (aka
> infinite lists)? More correctly, memo1 uses a pointer to the head of
> the list as a key, for fast comparing (as you can't compare infinite
> lists)? But since both key and value are infinite streams, won't this
> approach cause a serious space leak because the whole list cannot be
> reclaimed by the garbage collector? So the full ballPositionX and
> ballVelocityX streams would remain in memory, until the program exits?
>
> Since this doesn't happen when I run the SOE examples (I guess!), I
> clearly misunderstand this whole thing. I could explain it when the
> pointer to the list is actually a pointer to the delayed computation
> (a "thunk"?) of the tail, but the code doesn't seem to do that.
>
> Thanks for any help, I hope I explained the problem well enough.
>
> Peter Verswyvelen
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/haskell-cafe/attachments/20070924/6ba0e11f/attachment-0001.htm
More information about the Haskell-Cafe
mailing list