functions making their own memos

I've been wondering for some time how to handle a common use case in imperative programming, "static" variables that are accessed only inside one function, but not needed elsewhere. I think this probably has something to do with existential quantification, but in any case this is an area I'm ready to learn a lot more about. Any ideas are welcome along with suggested reading. Let's say that I have a function for generating random numbers every time it is called, but I sometimes want it to to re-use the last number depending on the time the last number was geneated: sometimesNew :: Time -> Int -> IO (Time,Int) sometimesNew lastTime lastValue = do currentTime <- <get time for system> if currentTime > lastTime + 10 then do newValue <- <get pseudorandom value> return (currentTime,newValue) else return (lastTime,lastValue) This function depends on the caller to remember its output (last time and last value) and feed that back to it. It would be nice if this function could make some kind of memo inside it itself and no one else needs to know. The case I'm working on right now is doing animation in Purescript on a canvas, in which I can control the motion of an object appearing in the animation by composing functions that control the motion. I might like to write a function that generates an overall circular trajectory, then compose it with a function that generates a slight wobbly motion. The wobbly function needs, basically, to change the motion randomly by interpolating between random positions and changing the random position only occassionally instead of during every animation frame. type MovementFunc = ... So this would generate a large circular movement: circularMovement :: MovementFunc This generates a small wobble. wobble :: MovementFunc Then I can compute the final position once per animation frame by superimposing or composing individual movements: computePosition :: [MovementFunc] -> Time -> IO Position The wobble function might like to have access to a memo of the last randomly chosen position, and how long ago in milliseconds it chose that position. Let's say that every movement function might like to have access to a memo, but the actual data type involved could be different from function to function. I also need to deal with initializing the memo. So the trick is to maintain and initialize these memo types without needing to know the internals of every function. Dennis

So this would generate a large circular movement:
circularMovement :: MovementFunc
This generates a small wobble.
wobble :: MovementFunc
Then I can compute the final position once per animation frame by superimposing or composing individual movements:
computePosition :: [MovementFunc] -> Time -> IO Position
Is there any specific reason why you can't compute the wobbles all at once beforehand? I'm thinking something along the lines of FRP: type Behaviour a = Time -> a type MovementFunc = Behaviour RelativeMovement So you still have wobble :: MovementFunc, but MovementFunc is pure. And you combine them not with (.) but with (<*>): mergeMovements :: [Behaviour RelativeMovement] -> Behaviour RelativeMovement mergeMovements = (mconcat .) . sequenceA computePosition :: [Behaviour RelativeMovement] -> Behaviour Position computePosition movements time = moveBy (mergeMovements movements time) origin Look, Ma, I'm still not using any IO! Wheeee! Cheers, MarLinn

In this one use case, there might be a simpler solution, but I have encountered lots of situations in which a particular algorithm could benefit from an associated stored cache of information. The most natural way to write an algorithm, sometimes, is in terms of its past decisions or past state of the program, interacting with current state. D

What about something like:
import Data.IORef ( IORef, newIORef, readIORef, writeIORef )
newCounter :: IO (IO Int)
newCounter = do
counterRef <- newIORef 0 :: IO (IORef Int)
let getCount :: IO Int
getCount = do
count <- readIORef counterRef
writeIORef counterRef (count + 1)
return count
return getCount
The user calls this function which creates the cache and returns the
stateful function. You can use MVars instead of IORefs if you want it to
work concurrently.
On Sun, Jul 8, 2018 at 2:29 AM Dennis Raddle
In this one use case, there might be a simpler solution, but I have encountered lots of situations in which a particular algorithm could benefit from an associated stored cache of information. The most natural way to write an algorithm, sometimes, is in terms of its past decisions or past state of the program, interacting with current state.
D _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Thanks, that's a cool solution. Here's another thought to avoid IO. What if
we're operating inside a state monad which has a list of memos. The
creation function newCounter then adds a memo to the list and creates
getCount as a closure with that index. The problem is that when we define
the state with its list of memos, we don't know what data type each
individual function will want to use.
D
On Sun, Jul 8, 2018 at 4:15 AM, Greg Horn
What about something like:
import Data.IORef ( IORef, newIORef, readIORef, writeIORef )
newCounter :: IO (IO Int) newCounter = do counterRef <- newIORef 0 :: IO (IORef Int) let getCount :: IO Int getCount = do count <- readIORef counterRef writeIORef counterRef (count + 1) return count return getCount
The user calls this function which creates the cache and returns the stateful function. You can use MVars instead of IORefs if you want it to work concurrently.
On Sun, Jul 8, 2018 at 2:29 AM Dennis Raddle
wrote: In this one use case, there might be a simpler solution, but I have encountered lots of situations in which a particular algorithm could benefit from an associated stored cache of information. The most natural way to write an algorithm, sometimes, is in terms of its past decisions or past state of the program, interacting with current state.
D _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.
participants (3)
-
Dennis Raddle
-
Greg Horn
-
MarLinn