1

In our CS-Lectures we currently learn about QuickCheck in Haskell. Now I got a task to use QuickCheck with the following tree-type:

data Tree = Leaf Int | Node Tree Tree
  deriving (Eq, Show)

I have already written some necessery equations to check different properties of trees. I know, that I need an instance of "Arbitrary" to run the whole thing. So tried this:

instance Arbitrary Tree where
   arbitrary = sized tree'
     where tree' 0 = do a <- arbitrary
                        oneof [return (Leaf a)]
           tree' n = do a <- arbitrary
                        oneof [return (Leaf a), return (Node (tree' (n-1)) (tree' (n-1)))]

But now I am getting some errors such as:

Couldn't match type `Gen Tree' with `Tree'
      Expected type: a -> Tree
        Actual type: a -> Gen Tree
    * In an equation for `arbitrary':
          arbitrary
            = sized tree'
            where
                tree' 0
                  = do a <- arbitrary
                       ....
                tree' n
                  = do a <- arbitrary
                       ....
      In the instance declaration for `Arbitrary Tree'
   |
61 |      where tree' 0 = do a <- arbitrary
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

or:

    * Couldn't match type `Tree' with `Gen Tree'
      Expected type: Int -> Gen Tree
        Actual type: Int -> Tree
    * In the first argument of `sized', namely tree'
      In the expression: sized tree'
      In an equation for `arbitrary':
          arbitrary
            = sized tree'
            where
                tree' 0
                  = do a <- arbitrary
                       ....
                tree' n
                  = do a <- arbitrary
                       ....
   |
60 |    arbitrary = sized tree'
   |                      ^^^^^

I think the problem is that I am doing some kind of recursion when choosing a Node. Because in that case the subtrees of that node are not trees but more like "return trees". Hopefully you know, what I mean.

Can somebody help me with this?

Thank you :)

Mr. Moose
  • 101
  • 8
  • What is `sized` here? It is not clear to me why you need to know the size in advance. – Willem Van Onsem Jan 18 '21 at 13:38
  • @WillemVanOnsem `sized` is provided by `QuickCheck`. It is important for tree-like data structures because of how easy it is to define a distribution with infinite expected size. – Daniel Wagner Jan 18 '21 at 15:11

2 Answers2

2

The simplest way to implement this is with:

instance Arbitrary Tree where
   arbitrary = frequency [
       (3, Leaf <$> arbitrary)
     , (1, Node <$> arbitrary <*> arbitrary)
     ]

Here the arbitrary functions in bold are the ones implement for the Tree instance. The arbitrary for Leaf is the arbitrary instance for an Int.

Here we thus specify that an arbitrary tree is a leaf with an arbitrary Int, or it is a Node with an arbitrary left and right sub-Tree.

or with sized :: (Int -> Gen a) -> Gen a:

instance Arbitrary Tree where
   arbitrary = sized go
        where go 0 = Leaf <$> arbitrary
              go n = oneof [Leaf <$> arbitrary, Node <$> go' <*> go']
                  where go' = go (n-1)

here the size specifies the depth of the tree, not the number of elements.

Iceland_jack
  • 6,848
  • 7
  • 37
  • 46
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • This distribution has infinite expected size. Calling the expected number of `Leaf`s produced by `arbitrary` *x*, we have the equation *x = (1/2)*1 + (1/2)*(x + x) = 1/2 + x*, which has no finite solution. – Daniel Wagner Jan 18 '21 at 15:14
  • @DanielWagner: ah yes, by altering the weights, normally the expected size should be two now, since `x = 3/4 + (1+2*x)/4` for the number of "data constructors", or 3/2 for the number of items. – Willem Van Onsem Jan 18 '21 at 15:26
0

This can be derived using the generic-random library

{-# Language DataKinds     #-}
{-# Language DeriveGeneric #-}
{-# Language DerivingVia   #-}

import GHC.Generics
import Generic.Random.DerivingVia
import Test.QuickCheck

-- ghci> :set -XTypeApplications
-- ghci> sample @Tree arbitrary
-- Node (Leaf 0) (Node (Leaf 0) (Node (Leaf 0) (Node (Node (Leaf 0) (Leaf 0)) (Leaf 0))))
-- Leaf 0
-- Leaf (-2)
-- Leaf 5
-- Leaf 0
-- Leaf 2
-- Leaf 1
-- Leaf 7
-- Node (Leaf (-7)) (Leaf (-2))
-- Node (Leaf 4) (Node (Leaf 0) (Leaf 3))
-- Node (Leaf 5) (Leaf (-2))
data Tree = Leaf Int | Node Tree Tree
  deriving
  stock (Eq, Show, Generic)

  deriving Arbitrary
  via GenericArbitraryRec '[2, 1] Tree

Let me know if there is something wrong with the distribution!

Iceland_jack
  • 6,848
  • 7
  • 37
  • 46