Когда вы говорите «модульный тест», я представляю что-то вроде 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
и b
?
Это важный аспект дизайна reactive-banana
. Например, это упрощает написание привязок к императивной структуре графического интерфейса. Магия reactive-banana
заключается в том, чтобы смешать все побочные эффекты в, как это называется в документации, «одну огромную функцию обратного вызова».
Кроме того, обычно сеть событий тесно связана с самим графическим интерфейсом. Рассмотрим Arithmetic
пример, где bInput1
и bInput2
создаются с использованием фактических виджетов ввода, а вывод привязан к output
, другому виджету.
Можно было бы создать систему тестирования, используя методы «насмешки», как и в других языках. Вы можете заменить фактические привязки GUI привязками к чему-то вроде pipes-concurrency
. Я не слышал, чтобы кто-то так делал.
Как проводить модульное тестирование: абстрагироваться от логики
Более того, вы можете и должны писать как можно больше логики вашей программы в отдельных функциях. Если у вас есть два входа типов inA
и 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
с помощью 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
, хотя вам пришлось бы делать это в монаде Moment
. Однако в версии 0.8 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)
Это должно сработать.
Сравнение с другими фреймворками FRP
Сравните это с другими фреймворками, такими как mvc
Габриэля Гонсалеса или netwire
. mvc
требует, чтобы вы написали логику вашей программы как состояние, но в остальном чистое Pipe a b (State s) ()
, а netwire
сети имеют тип Wire s e m a b
; в обоих случаях типы a
и b
предоставляют ввод и вывод из вашей сети. Это упрощает тестирование, но исключает встроенные привязки графического интерфейса, доступные с reactive-banana
. Это компромисс.
person
Christian Conkle
schedule
30.11.2014