Question

What I mean by first-order constraint

First, I'll explain what I mean by first-order constraint on arrows: Due to the way arrows desugar, you cannot use a locally bound name where an arrow command is expected in the arrow do-notation.

Here is an example to illustrate:

proc x -> f -< x + 1 desugars to arr (\x -> x + 1) >>> f and similarly proc x -> g x -< () would desugar to arr (\x -> ()) >>> g x, where the second x is a free variable. The GHC user guide explains this and says that when your arrow is also a monad you may make an instance of ArrowApply and use app to get around this. Something like, proc x -> g x -<< () becomes arr (\x -> (g x, ())) >>> app.

My Question

Yampa defines the accumHold function with this type: a -> SF (Event (a -> a)) a. Due to this first-order limitation of arrows, I'm struggling to write the following function:

accumHoldNoiseR :: (RandomGen g, Random a) => (a,a) -> g -> SF (Event (a -> a)) a
accumHoldNoiseR r g = proc f -> do
  n <- noiseR r g -< ()
  accumHold n -< f

The definition above doesn't work because n is not in scope after desugaring.

Or, similarly this function, where the first part of the pair to SF is meant to be the initial value passed to accumHold

accumHold' :: SF (a,Event (a -> a)) -> a
accumHold' = ...

Is there some combinator or trick that I'm missing? Or is it not possible to write these definitions without an ArrowApply instance?

tl;dr: Is it possible to define accumHoldNoiseR :: (RandomGen g, Random a) => (a,a) -> g -> SF (Event (a -> a)) a or accumHold' :: SF (a,Event (a -> a)) -> a in yampa?

Note: There is no instance of ArrowApply for SF. My understanding is that it doesn't make sense to define one either. See "Programming with Arrows" for details.

Was it helpful?

Solution

This is a theoretical answer. Look to Roman Cheplyaka's answer to this question, which deals more with the practical details of what you're trying to achieve.


The reason n is out of scope is that for it to be in scope to use there, you would have the equivalent of bind or >>= from monads. It's the use of the results of a previous computation as a functional input to the next which makes something as powerful as a monad.

Hence you can supply n as a function argument to a subsequent arrow exactly when you can make an ArrowApply instance.

Chris Kuklewicz correctly points out in his comment that -<< would bring n into scope - it also uses app, so you need an ArrowApply instance.

Summary

Not unless you use ArrowApply. This is what ArrowApply is for.

OTHER TIPS

noiseR is a signal function; it produces a stream of random numbers, not just one random number (for that, you'd just use randomR from System.Random).

On the other hand, the first argument of accumHold is just one, initial, value.

So this is not just some limitation — it actually prevents you from committing a type error.

If I understand correctly what you're trying to do, then simply using randomR should do the trick. Otherwise, please clarify why you need noiseR.

To help others understand how I worked around this I'll answer my own question.

I was trying to implement the game pong. I wanted the ball to start with a random velocity each round. I wanted to use accumHold to define the ball's velocity. I had some code like this:

ballPos = proc e -> mdo -- note the recursive do
  {- some clipping calculations using (x,y) -}
  ...
  vx <- accumHold 100 -< e `tag` collisionResponse paddleCollision
  vy <- accumHold 100 -< e `tag` collisionResponse ceilingFloorCollision
  (x,y) <- integral -< (vx,vy)
  returnA -< (x,y)

I wanted to replace the 100s with random values (presumably from noiseR).

How I solved this instead is to accumulate over the direction, where collisionResponse just flips the sign (eventually I'll want to use the angle of the velocity relative to wall/paddle):

ballPos = proc (initV, e) -> mdo
  {- some clipping calculations using (x,y) -}
  ...
  (iVx,iVy) <- hold (0,0) -< initV
  vx <- accumHold 1 -< e `tag` collisionResponse paddleCollision
  vy <- accumHold 1 -< e `tag` collisionResponse ceilingFloorCollision
  (x,y) <- integral -< (iVx*vx,iVy*vy)
  returnA -< (x,y)

Lesson Learned:

You can often separate the value/state you want to accumulate into a behavior describing how it changes and a "magnitude" that describes its current value taking the behavior as input. In my case, I separate out the magnitude of the initial velocity, pass that as input to the signal function, and use accumHold to compute the affect on the ball (the behavior) of having collisions. So regardless of what the initial velocity was, hitting the walls "reflects" the ball. And that's exactly what the accumHold is accumulating.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top