Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generically derive Arbitrary for massive algebraic data types?

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?

like image 625
carpemb Avatar asked Jun 15 '16 18:06

carpemb


1 Answers

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
like image 191
carpemb Avatar answered Nov 06 '22 13:11

carpemb