Generically derive Arbitrary for massive algebraic data types?
Asked Answered
S

1

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?

Spoliation answered 15/6, 2016 at 18:29 Comment(6)
hackage.haskell.org/package/generic-random ?Hyperon
Hmm, I'm looking at it right now. It seems to be going in the direction I need.Spoliation
This functionality is also available in regular-extras. I don't know how it compares to generic-random.Dilatometer
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`Serotherapy
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.Spoliation
there is a PR to add generic derivation to QuickCheck, but it needs some care: github.com/nick8325/quickcheck/pull/40Flattop
S
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
Spoliation answered 25/9, 2016 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.