dan*_*ght 5 haskell frp netwire
我一直想给FRP一点时间,昨天我终于咬了一口,然后开始使用Netwire 5开始(这本身就是一个相当随意的选择,但我必须从某个地方开始!).我已经设法达到了"有效的代码"这一点,但我注意到了一些模式,我不确定它们是如何使用库的一部分,或者它们是否是我的症状.在某处做错了什么.
我从这个教程开始,这足以让我很容易地启动和运行 - 我现在有一个由简单的"递增数字"线控制的旋转立方体:
spin :: (HasTime t s, Monad m) => Wire s e m a GL.GLfloat
spin = integral 0 . 5
Run Code Online (Sandbox Code Playgroud)
当按下"Esc"时,应用程序将退出,使用netwire-input-glfw中提供的电线:
shouldQuit :: (Monoid e, Functor m, Monad m) => Wire s e (GLFWInputT m) a a
shouldQuit = keyPressed GLFW.Key'Escape
Run Code Online (Sandbox Code Playgroud)
这些之间的一个重要区别是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
Run Code Online (Sandbox Code Playgroud)
这种模式有两件事让我觉得不舒服.首先,我传递Right undefined给两个电话的事实stepWire.我认为(如果我的理解是正确的)这个参数用于将事件发送到电线,并且因为我的电线没有使用任何事件这是"安全的",但感觉很糟糕(编辑可能是"事件"是这里错误的单词 - 教程将其描述为"阻塞值",但这一点仍然存在 - 我从不打算阻止并且不在e我的线路中的任何地方使用参数).我查看是否有一个版本stepWire的情况,你知道你从来没有一个事件,即使你有一个,你也不会回应它,但看不到一个.我尝试制作导线e参数()然后传递到Right ()任何地方,感觉边缘不那么脏undefined,但似乎仍然不能代表我的意图.
同样,返回值也是一个Either.这对于shouldQuit电线是完美的,但请注意我必须模式匹配wt',spin电线的输出.我真的不知道抑制它意味着什么,所以我只是return (),但我可以想象,随着电线数量的增加,这变得笨拙,而且,这似乎并不代表我的意图 -有一根永不抑制的电线,我总能依靠它来保持下一个价值.
因此,尽管我的代码有效,但我仍然感到不安的感觉是"我做错了",而且由于Netwire 5相当新,所以我很难找到可以检查的"惯用"代码的例子.看看我是否接近标记.这是图书馆的使用方式,还是我错过了什么?
编辑:我已经设法通过组合和单个解决我提到的第二个问题(模式匹配的Either结果spin):spinshouldQuitWire
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
Run Code Online (Sandbox Code Playgroud)
踩到这条线给了我一个更明智的回报值 - 如果它Left我可以退出,否则我有一块数据可以使用.它还暗示了比我原始方法更大程度的可组合性.
我仍然需要传递Right undefined给这条新线路作为输入.不可否认,现在只有其中一个,但我仍然不确定这是否是正确的方法.
在程序的最顶层,您将拥有一些具有(缩写)类型的连线Wire a b。这需要传递一些类型的东西,并且每次你采取一步时a它都会返回一些类型的东西。b例如,两者a都b可以是WorldState游戏的一部分,也可以[RigidBody]是物理模拟器的一部分。我认为通过就可以了Right undefined顶级水平就可以了。
话虽如此,您忽略了输入线Alternative的重要实例。Wire a b它提供了一个<|>以非常好的方式工作的运算符:
假设我们有两根电线:
w1 :: Wire a b
w2 :: Wire a b
Run Code Online (Sandbox Code Playgroud)
如果 w1 抑制,则
w1 <|> w2 == w2
Run Code Online (Sandbox Code Playgroud)
如果w1不抑制,那么
w1 <|> w2 == w1
Run Code Online (Sandbox Code Playgroud)
这意味着只有当和都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)
Run Code Online (Sandbox Code Playgroud)
当您按下 时F,您的旋转速度会加倍!
如果你想在按下按钮后改变连线的语义,你必须更有创意,但不要太多。如果您的电线表现不同,则意味着您正在进行某种切换。开关的文档主要要求您遵循类型。
这是一条电线,它的作用类似于身份线,直到您按下给定的键,然后将永远禁止:
trigger :: GLFW.Key -> GameWire a a
trigger key =
rSwitch mkId . (mkId &&& ((now . pure mkEmpty . keyPressed key) <|> never))
Run Code Online (Sandbox Code Playgroud)
有了这个,你可以做一些很酷的事情,比如:
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
Run Code Online (Sandbox Code Playgroud)
每当您点击 时,这都会在前进和后退之间切换旋转器F。