如何解决箭头上的一阶约束?

7

我所指的一阶限制

首先,我将解释一下我所说的箭头上的一阶限制: 由于箭头展开的方式,您无法在箭头do-notation中期望箭头命令的地方使用局部绑定名称。

这里有一个例子可以说明:

proc x -> f -< x + 1 展开为 arr (\x -> x + 1) >>> f,类似地,proc x -> g x -< () 将展开为 arr (\x -> ()) >>> g x,其中第二个 x 是自由变量。 GHC用户指南 解释了这一点,并表示当您的箭头也是monad时,您可以创建 ArrowApply 实例并使用 app 来解决此问题。例如,proc x -> g x -<< () 变成了 arr (\x -> (g x, ())) >>> app

我的问题

Yampa使用以下类型定义 accumHold 函数: a -> SF (Event (a -> a)) a。 由于箭头的这个一阶限制,我很难编写下面的函数:

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

上面的定义不起作用,因为在desugaring后,n不在作用域内。
或者,类似地,对于这个函数,SF对应的一对中的第一个值应该是传递给accumHold的初始值。
accumHold' :: SF (a,Event (a -> a)) -> a
accumHold' = ...

有没有组合器或技巧我漏掉了?或者是不可能在没有ArrowApply实例的情况下编写这些定义吗? tl;dr:是否可以定义accumHoldNoiseR :: (RandomGen g, Random a) => (a,a) -> g -> SF (Event (a -> a)) aaccumHold' :: SF (a,Event (a -> a)) -> a在yampa中? 注意:SF没有ArrowApply实例。我的理解是也不可能定义一个。有关详细信息,请参见"Programming with Arrows"

2
我不是箭头专家,所以我不知道你是否需要ArrowApply,但解决这个问题的一种方法是问问自己,如果没有一阶限制,你认为你的代码应该展开成什么样子。 - Gabriella Gonzalez
在“accumHold n -< f”中使用“n”超出了范围,但我怀疑“-<<”可以替换“-<”,使“n”处于范围内。 - Chris Kuklewicz
@ChrisKuklewicz -<< 需要一个 ArrowApply 实例,因此在这里没有意义(即,您不能将 SF 变成一个单子)。 - Jason Dagit
3个回答

3
这是一篇理论性的回答。请参考Roman Cheplyaka的答案,该答案更多地涉及到您试图实现的实际细节。
变量n超出了作用域的原因是,要在那里使用它,你需要像单子中的bind>>=一样,将其与前一个计算的结果作为下一个计算的函数输入。正是这种机制使得单子如此强大。
因此,只有当您可以创建一个ArrowApply实例时,才能将n作为函数参数提供给后续箭头。
Chris Kuklewicz在他的评论中正确地指出,-<<会将n带入作用域,但它也使用了app,所以您需要一个ArrowApply实例。

总结

只有在使用ArrowApply时才能将变量n带入作用域。这就是ArrowApply的作用。

2

noiseR 是一个信号函数;它会产生一串随机数,而不仅仅是一个随机数(如果只需要一个随机数,可以使用 System.Random 中的 randomR)。

另一方面,accumHold 的第一个参数只是一个初始值。

所以这不仅仅是一些限制 - 它实际上防止了您犯类型错误。

如果我正确理解您的意思,那么简单地使用 randomR 应该就可以了。否则,请澄清为什么需要使用 noiseR


我在accumHoldNoiseR函数中使用了randomR(而不是noiseR),这会给我一个局部绑定,因此我将无法使用它。 - Jason Dagit

0
为了帮助其他人理解我是如何解决这个问题的,我将回答自己的问题。
我正在尝试实现乒乓球游戏。我希望每一轮球都以随机速度开始。我想使用accumHold来定义球的速度。我的代码类似于这样:
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)

我想用随机值(可能来自noiseR)替换100。

我解决这个问题的方法是在方向上累加,其中collisionResponse只是翻转符号(最终我将使用相对于墙/挡板的速度角度):

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)

经验教训:

在编程中,你常常可以将要累积的值/状态分成描述其变化的行为和描述其当前值的“量”,并将其作为输入传递给信号函数。在我的案例中,我将初始速度的大小分离出来,将其作为输入传递给信号函数,并使用accumHold计算与撞击相应的球体(行为)的影响。因此,无论初始速度是什么,撞到墙上都会“反弹”球。这就是accumHold正在累积的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接