Cal*_*ers 6 haskell frp reactive-programming reactive-banana
有没有办法在反应性香蕉中创建单元测试网络?假设我已经使用一些输入事件构建了一些网络 - 是否有可能验证事件是否产生了一些输出流/行为在一些输入事件之后具有某些值.这样做甚至有意义吗?
我注意到有各种interpret*功能,但似乎无法解决如何使用它们.还有Model一个看起来非常适合测试的模块,但它与真正的实现有完全不同的类型.
当你说"单元测试"时,我想象的是QuickCheck,在网络中注入大量输入并检查输出.要做这样的事情,我们需要一个功能:
evalNetwork :: Network a b -> [a] -> IO [b]
Run Code Online (Sandbox Code Playgroud)
在这个答案的最后,我演示了interpret*一个类型相似的函数之一的变体,用于特定类型的"网络".
reactive-banana网络类型的Mumbo-jumbo这样的功能与使用的"整个网络"的实际类型不兼容reactive-banana.对比涉及网络的实际功能的类型:
compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork
Run Code Online (Sandbox Code Playgroud)
所以任何网络的类型都是forall t. Frameworks t => Moment t ().没有类型变量; 没有输入或输出.同样,该EventNetwork类型没有参数.这告诉我们所有的输入和输出都是通过副作用来处理的IO.这也意味着不能真正有这样的功能
interpret? :: EventNetwork -> [a] -> IO [b]
Run Code Online (Sandbox Code Playgroud)
因为会有什么a和将来的b?
这是设计的一个重要方面reactive-banana.例如,它可以轻松地将命令绑定到命令式GUI框架.神奇的reactive-banana是洗牌周围的所有副作用之中,作为文档称之为"一个单一的,巨大的回调函数."
此外,事件网络通常与GUI本身密切相关.考虑一下这个Arithmetic例子,其中bInput1和bInput2使用实际的输入小部件构建,并且输出绑定到output另一个小部件.
与其他语言一样,可以使用"模拟"技术构建测试工具.您可以将实际的GUI绑定替换为类似的绑定pipes-concurrency.我没有听说有人这样做过.
更好,你可以而且应该在不同的函数中尽可能多地编写程序逻辑.如果你有两个输入,类型inA和inB,和一个类型的输出out,也许你可以写一个像这样的函数
logic :: Event t inA -> Event t inB -> Behavior t out
Run Code Online (Sandbox Code Playgroud)
这几乎是使用的正确类型interpretFrameworks:
interpretFrameworks :: (forall t. Event t a -> Event t b) ->
[a] -> IO [[b]]
Run Code Online (Sandbox Code Playgroud)
您可以Event使用split(或者更确切地说,将输入拆分为Event所需的两个输入)来组合两个输入logic.现在你有了logic' :: Event t (Either inA inB) -> Behavior t out.
你将输出Behavior转换成了一种阻碍Event.在0.7版本中,changes函数Reactive.Banana.Frameworks有Frameworks t => Behavior t a -> Moment t (Event t a)必须用于解包的类型Behavior,尽管你必须在Momentmonad中进行.然而,在0.8版本中,它a被包装为a Future a,其中Future是未导出的类型.(Github重新出口Future存在问题.)
解包的最简单方法Behavior可能只是重新实现interpretFrameworks适当的类型.(请注意,它返回一个包含初始值和后续值列表的元组.)即使Future未导出,也可以使用其Functor实例:
interpretFrameworks' :: (forall t. Event t a -> Behavior t b)
-> [a] -> IO (b, [[b]])
interpretFrameworks' f xs = do
output <- newIORef []
init <- newIORef undefined
(addHandler, runHandlers) <- newAddHandler
network <- compile $ do
e <- fromAddHandler addHandler
o <- changes $ f e
i <- initial $ f e
liftIO $ writeIORef init i
reactimate' $ (fmap . fmap) (\b -> modifyIORef output (++[b])) o
actuate network
bs <- forM xs $ \x -> do
runHandlers x
bs <- readIORef output
writeIORef output []
return bs
i <- readIORef init
return (i, bs)
Run Code Online (Sandbox Code Playgroud)
这应该可以解决问题.
与其他框架(如Gabriel Gonzalez mvc或ErtugrulSöylemez's)形成对比netwire.mvc要求您将程序逻辑编写为有状态但纯粹的Pipe a b (State s) (),并且netwire网络具有类型Wire s e m a b; 在这两种情况下都是类型,a并向网络b公开输入和输出.这使您可以轻松进行测试,但排除了可用的"内联"GUI绑定reactive-banana.这是一个权衡.