5

我一直想试一试 FRP,昨天我终于硬着头皮试了一下,使用 Netwire 5 开始(这本身就是一个相当随意的选择,但我必须从某个地方开始!)。我已经设法达到“有效的代码”的目的,但我注意到一些模式,我不确定这些模式是否是预期如何使用库的一部分,或者它们是否是我的症状我在某处做错了什么。

我从本教程开始,这足以让我轻松启动和运行——我现在有一个由简单的“递增数字”线控制的旋转立方体:

spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . 5

当按下“Esc”时,应用程序将退出,使用netwire-input-glfw中提供的电线:

shouldQuit :: (Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a a
shouldQuit = keyPressed GLFW.Key'Escape

它们之间的一个重要区别是spin从不禁止——它应该总是返回一些值——而一直shouldQuit禁止;直到实际按下键,在这种情况下我退出应用程序。

让我感到不安的是我最终不得不使用这些电线的方式。现在,它看起来像这样:

(wt', spinWire') <- stepWire spinWire st $ Right undefined
((qt', quitWire'), inp'') <- runStateT (stepWire quitWire st $ Right undefined) inp'

case (qt', wt') of
  (Right _, _) -> return ()
  (_, Left _)  -> return () -- ???
  (_, Right x) -> --do things, render, recurse into next frame

这种模式有两点让我感到不舒服。首先,我将Right undefined两个调用都传递给stepWire. 我认为(如果我的理解是正确的)该参数用于将事件发送到线路,并且由于我的线路不使用任何事件,这是“安全的”,但感觉很糟糕(编辑也许“事件”是这里的错误词——本教程将其描述为“阻塞值”,但重点仍然存在——我从不打算阻塞,也不打算e在我的线路中的任何地方使用参数)。我查看了是否有一种版本,stepWire用于您知道自己从未发生过事件并且即使您确实有事件但看不到事件也不会响应它的情况。e()然后Right ()到处经过,感觉比 稍微脏一点undefined,但似乎仍然不能完全代表我的意图。

同样,返回值也是一个Either. 这对shouldQuit电线来说是完美的,但请注意我必须wt'在电线的输出上进行模式匹配spin。我真的不知道这意味着什么,所以我只是return (),但我可以想象随着电线数量的增加,这会变得笨拙,而且,它似乎并不代表我的意图——有一根永远不会抑制的电线,我可以依靠它来保持下一个值。

因此,尽管我有可以工作的代码,但我还是有一种不安的感觉,即我以某种方式“做错了”,而且由于 Netwire 5 是相当新的,因此很难找到我可以检查的“惯用”代码示例和看看我是否接近标记。这是该库的用途还是我遗漏了什么?

编辑:我已经设法解决了我提到的第二个问题(Either结果的模式匹配spin),方法是将spinshouldQuit合并为一个Wire

shouldContinuePlaying :: (Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a a
shouldContinuePlaying = keyNotPressed GLFW.Key'Escape

game :: (HasTime t s, Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a GL.GLfloat
game = spin . shouldContinuePlaying

踩这条线给了我一个更明智的返回值——如果Left我可以退出,否则我有数据可以使用。它还暗示了比我原来的方法更大程度的可组合性。

不过,我仍然必须将Right undefined其作为输入传递给这条新电线。诚然,现在只有一个,但我仍然不确定这是否是正确的方法。

4

1 回答 1

1

在程序的最顶层,您将有一些具有(缩写)类型的线Wire a b。这需要传递一些类型的东西,每次你迈出一步a它都会返回一些类型的东西。b例如,两者ab都可以是WorldState游戏或[RigidBody]物理模拟器的。在我看来,通过Right undefined顶级水平是可以的。

话虽如此,您忽略了输入线的重要Alternative实例。Wire a b它提供了一个<|>以非常好的方式工作的运算符:

假设我们有两条线:

w1 :: Wire a b
w2 :: Wire a b

如果 w1 禁止,则

w1 <|> w2 == w2

如果 w1 不禁止,则

w1 <|> w2 == w1

这意味着只有在两者都禁止w1 <|> w2时才会禁止。太好了,这意味着我们可以执行以下操作: w1w2

spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . (10 . keyPressed GLFW.Key'F <|> 5)

当您按下 时F,您会以两倍的速度旋转!

如果你想在按下按钮后改变电线的语义,你必须更有创意,但不多。如果你的电线表现不同,这意味着你正在做某种开关。开关的文档主要要求您遵循 types

在您按下给定键之前,这条线将充当身份线,然后将永远禁止:

trigger :: GLFW.Key -> GameWire a a
trigger key =
  rSwitch mkId . (mkId &&& ((now . pure mkEmpty . keyPressed key) <|> never))

有了这个,你可以做一些很酷的事情,比如:

spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . spinSpeed
  where
    spinSpeed = 5 . trigger GLFW.Key'F --> 
                -5 . trigger GLFW.Key'F -->
                spinSpeed

每当您点击 时,这将在前进和后退之间切换微调器F

于 2015-03-08T08:46:21.377 回答