quickcheck-higherorder icon indicating copy to clipboard operation
quickcheck-higherorder copied to clipboard

More Constructible instances

Open Lysxia opened this issue 7 years ago • 6 comments

  • Types from base and QuickCheck
  • Generic types

Lysxia avatar Feb 15 '18 01:02 Lysxia

How much work would the generic types be?

isovector avatar Jun 25 '20 15:06 isovector

It seems pretty straightforward to get to a working state, but I have some minor concerns about packaging and the API around Arbitrary.

The main and straightforward part is to define Repr a and fromRepr by induction on the generic representation Rep a, GHC.Generics 101-level recipe, no surprises there.

For the superclass constraints Arbitrary (Repr a) and Show (Repr a) you can pull in generic-random and generic-data, which brings me to the following points:

  1. They are extra dependencies, and they seem heavy enough to put this machinery in (yet) another package.
  2. For Arbitrary I imagine most users would already be content with some default settings, but it also doesn't seem too hard to make available the various knobs of generic-random.

(2) could just be me overthinking. (1) needs some decision to be made, but if you agree with it you can just create another package.

(EDIT: I forgot to mention shrinking, QuickCheck has a generic implementation we can just reuse)

Lysxia avatar Jun 25 '20 16:06 Lysxia

I think we could entirely offload those instances, right? eg instance Arbitrary a => Arbitrary (K1 _1 a) --- plus some newtype wrappers to not make this an orphan. I'm not sure why you'd need generic-random and generic-data.

isovector avatar Jun 25 '20 18:06 isovector

The problem is with sums, that you don't know with what probability to go left or right. You can set that probability arbitrarily, but by definition that's going to be arbitrary and inflexible.

generic-data would be used to derive Show for a type computed from a generic representation. For example, if you want a Constructible instance for:

data MyType = MyCons (Int -> Int)
  -- No instances

you somehow need to define its Repr:

data ReprMyType = ReprMyCons (Int :-> Int)
  deriving (Show, Arbitrary)

except that with generics you don't have the ability to declare data types, so what do you do?

Lysxia avatar Jun 25 '20 18:06 Lysxia

Hmm, that seems rather involved. Could you instead Replace (->) (:->) (Rep a) as the Repr, and then use from <$> arbitrary @a + some barbies magic to get the Arbitrary (Repr a) instance? This is essentially how higgledy works.

isovector avatar Jun 25 '20 18:06 isovector

That can work, but it's actually even more involved.

Could you instead Replace (->) (:->) (Rep a) as the Repr

I was imagining something even simpler, mapping Repr to every field (K1) of the Rep, and letting that take care of the substitution recursively.

and then use from <$> arbitrary @a + some barbies magic to get the Arbitrary (Repr a) instance?

It seems the main idea is to somehow make a function a -> Repr a, and then fmap that function over arbitrary; that is essentially what QuickCheck does for Fun. That can be done and added to this library too, but:

  • We'll have to copy QuickCheck's Function class (and many instances) and adapt it to test-fun; that's a much more involved task than deriving Arbitrary (Repr a) directly from the generic structure of Repr a.

  • it's only going to work for types containing at most first-order functions (more precisely, whose domains have decidable equality)

Lysxia avatar Jun 25 '20 19:06 Lysxia