Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

typeclass challenge: having both variadic arguments and results

While writing some Arbitrary instances, I implemented a couple of functions with the following quite mechanical pattern:

type A = Arbitrary -- to cut down on the size of the annotations below
shrink1 :: (A a          ) => (a           -> r) -> (a           -> [r])
shrink2 :: (A a, A b     ) => (a -> b      -> r) -> (a -> b      -> [r])
shrink3 :: (A a, A b, A c) => (a -> b -> c -> r) -> (a -> b -> c -> [r])

shrink1 f a     = [f a'     | a' <- shrink a]
shrink2 f a b   = [f a' b   | a' <- shrink a] ++ [f a b'   | b' <- shrink b]
shrink3 f a b c = [f a' b c | a' <- shrink a] ++ [f a b' c | b' <- shrink b] ++ [f a b c' | c' <- shrink c]

I wrote out these functions by hand up to shrink7, and that seems to be sufficient for my needs. But I can't help but wonder: can this reasonably be automated? Bonus points for a solution that:

  • allows for shrink0 f = []
  • generates all the shrinkers
  • has loads of typeclass hackery, I love that
  • skips the scary extensions like incoherent/undecidable/overlapping instances
  • lets me have my cake and eat it, too: doesn't require me to uncurry f when passing it in or curry the application shrinkX f when applying it to a, b, and c
like image 673
Daniel Wagner Avatar asked Aug 17 '12 23:08

Daniel Wagner


People also ask

How do you use variadic arguments?

It takes one fixed argument and then any number of arguments can be passed. The variadic function consists of at least one fixed variable and then an ellipsis(…) as the last parameter. This enables access to variadic function arguments. *argN* is the last fixed argument in the variadic function.

What symbols are used in a function declaration to indicate that it is a Variadic function?

A function with a parameter that is preceded with a set of ellipses ( ... ) is considered a variadic function. The ellipsis means that the parameter provided can be zero, one, or more values.

How do you write a Variadic function in C++?

Variadic functions are functions (e.g. std::printf) which take a variable number of arguments. To declare a variadic function, an ellipsis appears after the list of parameters, e.g. int printf(const char* format...);, which may be preceded by an optional comma.

What is the use of Variadic templates?

With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.


2 Answers

This compiles, I hope it works:

{-# LANGUAGE TypeFamilies #-}
import Test.QuickCheck

class Shrink t where
  type Inp t :: *
  shrinkn :: Inp t -> t
  (++*) :: [Inp t] -> t -> t

instance Shrink [r] where
  type Inp [r] = r
  shrinkn _ = []
  (++*) = (++) 

instance (Arbitrary a, Shrink s) => Shrink (a -> s) where
  type Inp (a -> s) = a -> Inp s
  shrinkn f a = [ f a' | a' <- shrink a ] ++* shrinkn (f a)
  l ++* f = \b -> map ($ b) l ++* f b

(++*) is only for implementing shrinkn.

Sorry for the relative lack of typeclass hackery. The [r] provides a nice stop condition for the type recursion, so hackery isn't needed.

like image 131
Sjoerd Visscher Avatar answered Oct 21 '22 06:10

Sjoerd Visscher


I doubt you can avoid scary extensions in this case, but otherwise:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, TypeFamilies,
 UndecidableInstances, IncoherentInstances #-}

import Test.QuickCheck

class Shrinkable a r where
    shrinkn :: a -> r

instance (Shrinkable [a -> b] r) => Shrinkable (a -> b) r where
    shrinkn f = shrinkn [f]

instance (Arbitrary a, Shrinkable [b] r1, r ~ (a -> r1)) => Shrinkable [a -> b] r where
    shrinkn fs@(f:_) a =
        let fs' = [f a | f <- fs]
        in shrinkn $ fs' ++ [f a' | a' <- shrink a]

instance (r ~ [a]) => Shrinkable [a] r where
    shrinkn (_:vs) = vs

instance (r ~ [a]) => Shrinkable a r where
    shrinkn e = []

Here are a few Quickcheck properties to test against your example functions:

prop0 a = shrinkn a == []

prop1 a = shrink1 not a == shrinkn not a 

prop2 a b = shrink2 (++) a b == shrinkn (++) a b 

f3 a b c = if a then b + c else b * c 
prop3 a b c = shrink3 f3 a b c == shrinkn f3 a b c 
like image 2
Ed'ka Avatar answered Oct 21 '22 07:10

Ed'ka