正确使用Netwire(5)

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

Mok*_*sha 1

在程序的最顶层,您将拥有一些具有(缩写)类型的连线Wire a b。这需要传递一些类型的东西,并且每次你采取一步时a它都会返回一些类型的东西。b例如,两者ab可以是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