【发布时间】:2023-06-17 16:47:01
【问题描述】:
我一直想试一试 FRP,昨天我终于硬着头皮试了一下,使用 Netwire 5 开始(这本身就是一个相当随意的选择,但我必须从某个地方开始!)。我已经设法达到“有效的代码”的目的,但我注意到一些模式,我不确定这些模式是否是预期如何使用库的一部分,或者它们是否是我的症状我在某处做错了什么。
我从this tutorial 开始,这足以让我轻松启动和运行——我现在有一个由简单的“递增数字”线控制的旋转立方体:
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 的两个调用。我认为(如果我的理解是正确的)该参数用于将事件发送到线路,并且由于我的线路不使用任何事件,这是“安全的”,但感觉很糟糕(EDIT 也许“事件”在这里是错误的词——本教程将其描述为“阻塞值”,但重点仍然存在——我从不打算阻塞,也不打算在任何地方使用 e 参数我的电线)。我查看了是否有stepWire 的版本,用于您知道自己从未有过事件并且即使您确实有事件但看不到事件也不会响应它的情况。我尝试制作电线e 参数(),然后到处传递Right (),感觉比undefined 稍微不那么脏,但似乎仍然不能代表我的意图。
同样,返回值也是Either。这非常适合shouldQuit 线,但请注意我必须在wt' 上进行模式匹配,spin 线的输出。我真的不知道这意味着什么,所以我只是return (),但我可以想象随着电线数量的增加,这会变得笨拙,而且,它似乎并不代表我的意图-- 拥有一条永不抑制的电线,我可以一直依靠它来保持下一个值。
因此,尽管我的代码可以运行,但我还是有一种不安的感觉,即我以某种方式“做错了”,而且由于 Netwire 5 相当新,因此很难找到我能找到的“惯用”代码示例检查一下,看看我是否接近标记。这是该库的用途还是我遗漏了什么?
编辑:通过将spin 和shouldQuit 组合成一个@,我设法解决了我提到的第二个问题(spin 的Either 结果的模式匹配) 987654345@:
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 作为输入传递给这条新线路。诚然,现在只有一个,但我仍然不确定这是否是正确的方法。
【问题讨论】:
-
我倾向于不在电线中使用效果。事件被传递给“主线”,而 monad 是多态的。