当你说“单元测试”时,我想象的是类似的东西QuickCheck
,您可以将多个输入注入网络并检查输出。为了做到这一点,我们需要一个类似以下的函数:
evalNetwork :: Network a b -> [a] -> IO [b]
在这个答案的最后,我展示了其中一个的变体interpret*
对于特定类型的“网络”,具有类似类型的函数。
胡言乱语reactive-banana
网络类型
这样的功能与实际使用的“整个网络”类型不兼容reactive-banana
。对比类型actual涉及网络的函数:
compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork
所以任意网络的类型都是forall t. Frameworks t => Moment t ()
。没有类型变量;没有输入或输出。同样,EventNetwork
类型没有参数。这告诉我们所有的输入和输出都是通过副作用来处理的IO
。这也意味着不可能有像这样的函数
interpret? :: EventNetwork -> [a] -> IO [b]
因为什么会a
and b
be?
这是设计的一个重要方面reactive-banana
。例如,它使得编写到命令式 GUI 框架的绑定变得容易。的魔力reactive-banana
就是将所有副作用混入文档中所说的“一个巨大的回调函数”。
此外,事件网络通常与 GUI 本身密切相关。考虑Arithmetic example https://github.com/HeinrichApfelmus/reactive-banana/blob/master/reactive-banana-wx/src/Arithmetic.hs, where bInput1
and bInput2
都是使用实际的输入小部件构建的,并且输出绑定到output
,另一个小部件。
可以像其他语言一样使用“模拟”技术来构建测试工具。您可以将实际的 GUI 绑定替换为类似的绑定pipes-concurrency
。我还没听说有人这样做。
如何进行单元测试:抽象出逻辑
更好的是,您可以而且应该在单独的函数中编写尽可能多的程序逻辑。如果您有两个输入,类型inA
and inB
,以及一个类型的输出out
,也许你可以写一个像这样的函数
logic :: Event t inA -> Event t inB -> Behavior t out
这几乎是正确的使用类型interpretFrameworks
:
interpretFrameworks :: (forall t. Event t a -> Event t b) ->
[a] -> IO [[b]]
您可以组合两个输入Event
s using split
(或者更确切地说,将输入分成两个Event
s 所要求的logic
)。现在你会有logic' :: Event t (Either inA inB) -> Behavior t out
.
你有点阻碍转换输出Behavior
to an Event
。在 0.7 版本中,changes
函数于Reactive.Banana.Frameworks
有类型Frameworks t => Behavior t a -> Moment t (Event t a)
,你可以用它来打开Behavior
,尽管你必须在Moment
单子。然而,在 0.8 版本中,a
被包裹成Future a
, where Future
是未导出的类型。 (有一个Github 重新导出的问题Future https://github.com/HeinrichApfelmus/reactive-banana/issues/70.)
最简单的解开方法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)
这应该可以解决问题。
与其他FRP框架的比较
将此与 Gabriel Gonzalez 等其他框架进行对比mvc http://hackage.haskell.org/package/mvc或 Ertugrul Söylemez 的netwire http://hackage.haskell.org/package/netwire. mvc
要求您将程序逻辑编写为有状态但纯粹的Pipe a b (State s) ()
, and netwire
网络有类型Wire s e m a b
;在这两种情况下的类型a
and b
公开网络的输入和输出。这使您可以轻松进行测试,但排除了可用的“内联”GUI 绑定reactive-banana
。这是一个权衡。