Testing Implementation vs Model - Records or Type Classes?

Hello, I'm writing a small Haskell library for functional reactive programming. The core of the library consists of two data types and several primitives. However, I have programmed this core *twice*: once as a *model* that displays the intended semantics, and once as the actual *implementation* to be used in production code. Of course, I would like to use QuickCheck to test that the implementation gives the same results as the model. My problem is: how to organize this with the minimum amount of boilerplate? It appears that I can use *multiparameter type classes* to solve this, but I'm not sure I'm happy with this, in particular because it makes the generated Haddock less readable (see 3) below). On the other hand, I could also use *record wildcards* to conveniently reify a module into a record data structure. But that will give me problems with combinators that are derived from the core combinators (see 2) below). Haskell Café, what are your suggestions and ideas? In particular, I wish to: 1) Write examples to be QuickChecked only once. I imagine that my main function for testing looks like this test_equal example = forAll $ \input -> example model input == example implementation input where example an expression involving the combinators from my library. The point is that I don't want to write example twice, once for each version. 2) Use derived combinators. For reference, here the full signature of the core combinators: data Event a data Behavior a instance Functor Behavior instance Applicative Behavior instance Functor Event instance Monoid (Event a) filter :: (a -> Bool) -> Event a -> Event a apply :: Behavior (a -> b) -> Event a -> Event b accumB :: a -> Event (a -> a) -> Behavior a When writing tests, I also want to use common derived combinators, like, say filterJust :: Event (Maybe a) -> Event a filterJust = fmap fromJust . filter isJust without implementing them twice. 3) Obtain readable Haddock. I like the simplicity and readability of an ordinary module signature. In contrast, libraries with many type class scare me. Or is there a way to make this less scary? I'm not sure about the last one: 4) Make both model and implementation available to the user, so that he can QuickCheck his own programs as well. Since the implementation makes heavy use of IO, it is a bit harder to test automatically, so the model might be useful to have. Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com

On 08/04/11 11:54, Heinrich Apfelmus wrote:
Hello,
I'm writing a small Haskell library for functional reactive programming. The core of the library consists of two data types and several primitives. However, I have programmed this core *twice*: once as a *model* that displays the intended semantics, and once as the actual *implementation* to be used in production code.
... Haskell Café, what are your suggestions and ideas?
... For reference, here the full signature of the core combinators:
data Event a data Behavior a
instance Functor Behavior instance Applicative Behavior instance Functor Event instance Monoid (Event a)
filter :: (a -> Bool) -> Event a -> Event a apply :: Behavior (a -> b) -> Event a -> Event b accumB :: a -> Event (a -> a) -> Behavior a
You don't need MPTCs to generalize the filter function: -- this class is useful beyond this FRP library, -- you might already be able to find it on hackage somewhere class Functor f => Filterable f where filter :: (a -> Bool) -> f a -> f a -- filter p . fmap f == fmap f . filter (p . f) -- filter (const True) == id -- filter p . filter q == filter (\x -> p x && q x) The apply and accumB functions are harder. Is the Behavior implementation for the model really different from the one of the implementation, which seems to be {initial::a, changes::Event a}? If not, you could just generalize that type by making the event type a parameter data GenBehavior e a = GB a (E a) If this is not the case, then instead of MPTCs you could also try type families, class ... => FRP event where data Behavior event apply :: Behavior event (a -> b) -> event a -> event b accumB :: a -> event (a -> a) -> Behavior event a I don't know whether this is any better than the MPTC approach, though. Twan

On 4/8/11 8:55 AM, Twan van Laarhoven wrote:
-- this class is useful beyond this FRP library, -- you might already be able to find it on hackage somewhere class Functor f => Filterable f where filter :: (a -> Bool) -> f a -> f a -- filter p . fmap f == fmap f . filter (p . f) -- filter (const True) == id -- filter p . filter q == filter (\x -> p x && q x)
There are a few other methods that should be added to this typeclass. In particular, filterMap :: (a -> Maybe b) -> f a -> f b which fuses the first rule and saves the redundant passes and evaluations. In a similar vein you may want a class for versions that allow applicative/monadic functions along with the sequenceA/sequence capabilities of Traversable. -- Live well, ~wren

Twan van Laarhoven wrote:
For reference, here the full signature of the core combinators:
data Event a data Behavior a
instance Functor Behavior instance Applicative Behavior instance Functor Event instance Monoid (Event a)
filter :: (a -> Bool) -> Event a -> Event a apply :: Behavior (a -> b) -> Event a -> Event b accumB :: a -> Event (a -> a) -> Behavior a
The apply and accumB functions are harder. Is the Behavior implementation for the model really different from the one of the implementation, which seems to be {initial::a, changes::Event a}? If not, you could just generalize that type by making the event type a parameter
data GenBehavior e a = GB a (E a)
If this is not the case,
I have changed the both implementations completely, so this no longer an option.
then instead of MPTCs you could also try type families,
class ... => FRP event where data Behavior event apply :: Behavior event (a -> b) -> event a -> event b accumB :: a -> event (a -> a) -> Behavior event a
I don't know whether this is any better than the MPTC approach, though.
Data type families have the advantage that I don't run into problems with ambiguity. The following seems sensible to me: class (Functor (Event f), Functor (Behavior f), Applicative (Behavior f)) => FRP f where apply :: Behavior f (a -> b) -> Event f a -> Event f b ... where f is simply a dummy variable to index different FRP implementations. The problem with this is that I need the FlexibleContexts extension to do that. There goes Haskell2010 compatibility. Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com
participants (3)
-
Heinrich Apfelmus
-
Twan van Laarhoven
-
wren ng thornton