12

I've got a protocol that I've typed like so:

data ProtocolPacket
  = Packet1 Word8 Text Int8
  | Packet2 Text
  | Packet3 Int Text Text Text Text
  | Packet4 Int Double Double Double Int16 Int16 Int16
  ...
  deriving (Show,Eq)

In addition, I've implemented serialization/deserialization code for each packet. Naturally, I would like to test this protocol in Quickcheck and make sure that serializing and deserializing any packet for any combination of inputs will give me back exactly what I put in. So I go ahead and implement these packets for the Arbitrary type class like so:

instance Arbitrary ProtocolPacket where
  arbitrary = do
  packetID <- choose (0x00,...) :: Gen Word8
  case packetID of
    0x00 -> do
      a <- arbitrary
      b <- arbitrary
      c <- arbitrary
      return $ Packet1 a b c
    0x01 -> do
      a <- arbitrary
      return $ Packet2 a
    0x02 -> do
      a <- arbitrary
      b <- arbitrary
      c <- arbitrary
      d <- arbitrary
      e <- arbitrary
      return $ Packet3 a b c d e
    0x03 -> do
      a <- arbitrary
      b <- arbitrary
      c <- arbitrary
      d <- arbitrary
      e <- arbitrary
      f <- arbitrary
      g <- arbitrary
      return $ Packet4 a b c d e f g
    ...

Assume that I've gone ahead and defined Arbitrary for all relevant data constructor arguments that don't have Arbitrary defined out of the box, such code needs to be hand-written by me in order for the packet fields to be populated with meaningful data. But that's it.

But as you can see, I'm repeating myself a lot for something that is just grunt work. And this is a small sample of what I'm actually dealing with. Ideally, I would like to be able to just do this:

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics

data ProtocolPacket
  = Packet1 Word8 Text Int8
  | Packet2 Text
  | Packet3 Int Text Text Text Text
  | Packet4 Int Double Double Double Int16 Int16 Int16
  ...
  deriving (Show,Eq,Generic)

instance Arbitrary ProtocolPacket

like I can do with FromJSON and ToJSON, but this doesn't work. Is there there a method that does?

carpemb
  • 691
  • 1
  • 8
  • 19
  • 6
    http://hackage.haskell.org/package/generic-random ? – Daniel Wagner Jun 15 '16 at 18:38
  • Hmm, I'm looking at it right now. It seems to be going in the direction I need. – carpemb Jun 15 '16 at 18:45
  • 2
    This functionality is also available in [regular-extras](https://hackage.haskell.org/package/regular-extras). I don't know how it compares to `generic-random`. – dfeuer Jun 15 '16 at 18:52
  • It might be interesting to look at `liftM2` operator and its siblings. Your case is the same as: ` case packetID of 0x00 -> liftM3 Packet1 a a a; 0x01 -> liftM Packet2 a; 0x02 -> liftM5 Packet3 a a a a a; 0x03 -> liftM7 Packet4 a a a a a a a; where a = arbitrary` – Centril Jun 15 '16 at 19:52
  • I've been playing around with generic-random and it's accomplishing what I had wanted, but I'm now getting `uncaught exception: ErrorCall (no representation.) Exception thrown while printing test case: 'No representation.'` from Quickcheck and I'm not sure what's generating it. Right now, all I can tell is that it doesn't like data constructors that have `ByteString` as an argument. I have no idea why. I'm currently testing it on pretty simple examples. – carpemb Jun 15 '16 at 19:57
  • 1
    there is a PR to add generic derivation to `QuickCheck`, but it needs some care: https://github.com/nick8325/quickcheck/pull/40 – phadej Jun 16 '16 at 19:06

1 Answers1

8

Daniel Wagner mentioned in the comments that generic-random is capable of doing this. It was the library I was looking for, but the docs didn't make it obvious to me that such was the case. Recent to the time of this writing, Brent Yorgey posted a pretty clear tutorial on his blog that went into detail as to how to use generic-random to do what I was asking about and more. The blog post can be found here.

For my case, the solution is simple. Using Generic.Random.Generic from generic-random:

{-# LANGUAGE DeriveGeneric #-}
import Generic.Random.Generic
import GHC.Generics

data ProtocolPacket
  = Packet1 Word8 Text Int8
  | Packet2 Text
  | Packet3 Int Text Text Text Text
  | Packet4 Int Double Double Double Int16 Int16 Int16
  ...
  deriving (Show,Eq,Generic)

instance Arbitrary ProtocolPacket where
  arbitrary = genericArbitrary
carpemb
  • 691
  • 1
  • 8
  • 19