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

问题描述 投票:0回答:3

一阶约束是什么意思

首先,我将解释箭头的一阶约束的含义: 由于箭头脱糖的方式,您不能在箭头 do 表示法中需要使用箭头命令的地方使用本地绑定名称。

这里举个例子来说明:

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

上面的定义不起作用,因为脱糖后

n
不在范围内。

或者,类似这个函数,其中

SF
对的第一部分意味着传递给
accumHold

的初始值
accumHold' :: SF (a,Event (a -> a)) -> a
accumHold' = ...

我是否缺少一些组合器或技巧?或者如果没有

ArrowApply
实例就不可能编写这些定义吗?

tl;dr: 是否可以在 yampa 中定义

accumHoldNoiseR :: (RandomGen g, Random a) => (a,a) -> g -> SF (Event (a -> a)) a
accumHold' :: SF (a,Event (a -> a)) -> a

注意:

ArrowApply
没有
SF
的实例。我的理解是,定义一个也没有意义。有关详细信息,请参阅 “使用箭头编程”

haskell yampa arrow-abstraction
3个回答
3
投票

这是一个理论上的答案。请参阅 Roman Cheplyaka 对这个问题的回答,它更多地涉及您想要实现的目标的实际细节。


n
超出范围的原因是,要使其在此处使用的范围内,您将拥有与 monad 中的
bind
>>=
等效的内容。它使用前一个计算的结果作为下一个计算的功能输入,这使得某些东西像 monad 一样强大。

因此,当您可以创建 ArrowApply 实例时,您可以将

n
作为函数参数提供给后续箭头。

Chris Kuklewicz 在 他的评论 中正确指出

-<<
会将
n
带入范围 - 它也使用
app
,因此您需要一个 ArrowApply 实例。

总结

除非您使用 ArrowApply,否则不会。这就是 ArrowApply 的用途。


2
投票

noiseR
是一个信号函数;它会产生随机数的,而不仅仅是一个随机数(为此,您只需使用
randomR
中的
System.Random
)。

另一方面,

accumHold
的第一个参数只是一个初始值。

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

如果我正确理解你想要做什么,那么只需使用

randomR
就可以解决问题。否则,请说明您为什么需要
noiseR


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)

我想用随机值替换 100(大概来自

noiseR
)。

我解决这个问题的方法是在方向上累积,其中

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
正在积累的。

© www.soinside.com 2019 - 2024. All rights reserved.