haskellarrowsyampa

How to work around the first-order constraint on arrows?


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.


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.