我一直想试一试 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
),方法是将spin
和shouldQuit
合并为一个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
其作为输入传递给这条新电线。诚然,现在只有一个,但我仍然不确定这是否是正确的方法。