functions making their own memos

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

functions making their own memos

Dennis Raddle
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











_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: functions making their own memos

MarLinn

> 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

_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: functions making their own memos

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.
Reply | Threaded
Open this post in threaded view
|

Re: functions making their own memos

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 <[hidden email]> 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.

_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: functions making their own memos

Dennis Raddle
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 <[hidden email]> wrote:
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 <[hidden email]> 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.


_______________________________________________
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.