我一直想给FRP一点时间,昨天我终于咬了一口子,然后开始使用Netwire 5开始(这本身是一个相当随意的选择,但我必须从某处开始!)。我已经设法达到"代码的作用"但是我注意到了一些模式,我不确定这些模式是预期如何使用库的一部分,或者它们是否是我在某处做错的症状。
我从this tutorial开始,这足以让我很轻松地开始运行 - 我现在有一个旋转的立方体,由一个简单的增加数字"丝:
spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . 5
并且当#34; 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
的调用。我认为(如果我的理解是正确的)这个参数用于将事件发送到电线,并且因为我的电线不能使用事件,所以这是安全的#34;但是感觉不好(编辑也许&#34;事件&#34;这里是错误的词 - 教程将其描述为&#34;阻止值&#34;,但重点仍然存在 - 我从来没有打算阻止并且不要在我的电汇中的任何地方使用e
参数。我查看是否有stepWire
的版本,因为您知道自己从未有过某个活动,即使您确实有活动,也不会对此做出回应,但是看不到一。我尝试制作电汇e
参数()
,然后在任何地方传递Right ()
,这感觉比undefined
稍微不那么脏,但仍然不能代表我意图。
同样,返回值也是Either
。这对于shouldQuit
线是完美的,但请注意我必须在wt'
线的spin
上进行模式匹配。我真的不知道抑制它意味着什么,所以我只是return ()
,但我可以想象随着电线数量的增加,这变得笨拙,而且它再也没有似乎是我的意图的代表 - 有一条永不抑制的电线,我可以依赖它始终保持下一个价值。
因此,尽管我的代码有效,但我仍然感到不安的感觉是“我做错了”#34;不知何故,由于Netwire 5相当新,很难找到&#34;惯用语&#34;我可以检查的代码,看看我是否在标记附近。这是图书的使用方式,还是我错过了什么?
编辑:我通过合并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
作为此新线路的输入。不可否认,现在只有其中一个,但我仍然不确定这是否是正确的做法。
答案 0 :(得分:1)
在你的程序的最顶层,你将有一些(缩写)类型Wire a b
的电线。这需要传递类型为a
的内容,并且每次执行此步骤时都会返回b
类型的内容。例如,a
和b
对于游戏可以是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
仅在 w1
和w2
禁止时才会禁止。这很棒,这意味着我们可以做以下事情:
spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . (10 . keyPressed GLFW.Key'F <|> 5)
当您按F
时,您的旋转速度会快两倍!
如果您想在按下按钮后更改电线的语义,您必须更有创意,但不是很多。如果您的电线表现不同,则意味着您正在进行某种切换。 documentation for switches主要要求您follow the 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
时,这将在前进和后退之间切换。