正确使用Netwire(5)

时间:2015-02-18 23:12:29

标签: haskell frp netwire

我一直想给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作为此新线路的输入。不可否认,现在只有其中一个,但我仍然不确定这是否是正确的做法。

1 个答案:

答案 0 :(得分: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时,您的旋转速度会快两倍!

如果您想在按下按钮后更改电线的语义,您必须更有创意,但不是很多。如果您的电线表现不同,则意味着您正在进行某种切换。 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时,这将在前进和后退之间切换。