maxloo
maxloo

Reputation: 491

Haskell monoid quickBatch tests: How to change mconcatP?

Based on the suggestion at Haskell quickBatch: Testing ZipList Monoid at mconcat results in stack overflow,

and some solutions at How can I constrain a QuickCheck parameter to a list of non-empty Strings?,

I've not been able to make this code workable:

instance Semigroup a 
  => Semigroup (ZipList a) where
    (<>) = liftA2 (<>)
instance Monoid a
  => Monoid (ZipList a) where
    mempty = pure mempty 
    mappend = liftA2 mappend
    mconcat as = 
      foldr mappend mempty as
mconcatP :: NonEmptyList a -> Property
mconcatP (nonEmptyList as) = mconcat as =-= 
  foldr mappend mempty as
nonEmptyList :: Gen [[Int]]
nonEmptyList = listOf1 (arbitrary :: Gen [Int])

zl :: ZipList (Sum Int)
zl = ZipList [1,1 :: Sum Int]
main :: IO ()
main = do 
  quickBatch $ monoid zl

I've also tried these:

import Test.QuickCheck 
  (NonEmptyList (..))
import Test.QuickCheck.Modifiers
  (NonEmptyList (..))

ghci will throw the error:

Parse error in pattern: nonEmptyList
mconcatP (nonEmptyList as) = mconcat as =-=

I've also tried:

mconcatP (NonEmptyList as) = mconcat as =-= 

The error thrown is:

Not in scope: data constructor ‘NonEmptyList’
Perhaps you meant variable ‘nonEmptyList’

I've also tried:

mconcatP :: (EqProp b, Monoid b) => 
  NonEmptyList b -> Property
mconcatP (getNonEmpty -> as) = 
  mconcat as =-= 
  foldr mappend mempty as

This enabled the running of the monoid tests, but the mconcat test still hang.

I've also tried:

mconcatP :: (EqProp b, Monoid b) => 
  NonEmptyList b -> Property
mconcatP (NonEmpty as) = mconcat as =-= 
  foldr mappend mempty as

This also enabled the running of the monoid tests, but the mconcat test still hang.

I've checked using :i NonEmptyList, the outcome:

newtype NonEmptyList a = NonEmpty {getNonEmpty :: [a]}
    -- Defined in ‘Test.QuickCheck.Modifiers’
instance Eq a => Eq (NonEmptyList a)
  -- Defined in ‘Test.QuickCheck.Modifiers’
instance Functor NonEmptyList
  -- Defined in ‘Test.QuickCheck.Modifiers’
instance Ord a => Ord (NonEmptyList a)
  -- Defined in ‘Test.QuickCheck.Modifiers’
instance Show a => Show (NonEmptyList a)
  -- Defined in ‘Test.QuickCheck.Modifiers’
instance Read a => Read (NonEmptyList a)
  -- Defined in ‘Test.QuickCheck.Modifiers’
instance Arbitrary a => Arbitrary (NonEmptyList a)
  -- Defined in ‘Test.QuickCheck.Modifiers’

My Cabal file is very simple:

-- testing.cabal
name: testing
version: 0.1.0.0
license-file: LICENSE
author: Max
maintainer: maxloo
build-type: Simple
cabal-version: >=1.10

library
  exposed-modules: Testing
  ghc-options: -Wall -fwarn-tabs
  build-depends: base >=4.7 && <5
      , hspec
      , QuickCheck
      , checkers
  hs-source-dirs: .
  default-language: Haskell2010

Why does it not work? How do I change mconcatP's code to enable the mconcat test from the 5 quickBatch monoid tests to run without stack overflow?

Upvotes: 0

Views: 75

Answers (1)

DDub
DDub

Reputation: 3924

When you write

functionName pat1 = result

then GHC expects that pat1 is a pattern. Patterns are very specific things that need to either be identifier names that get bound to inputs or constructors that get matched to data type shapes (and also the extensions ViewPatterns and PatternSynonyms that extend this further).

So, when you write

mconcatP (nonEmptyList as) = as

then GHC becomes very confused. nonEmptyList is not a constructor names (constructors always start uppercase), and because it's in parentheses with another identifier, it's not a lone identifier to bind input. Therefore, GHC concludes rightly that this isn't a valid pattern.

What you're looking for is:

mconcatP (NonEmpty as) = ...

There are a few alternatives you could write too! For instance, with ViewPatterns enabled, you could write:

mconcatP (getNonEmptyList -> as) = ...

This is a "view pattern", which tells GHC to take the argument, apply the function getNonEmptyList to it, and then bind the result to the identifier as.

Since this is QuickCheck, you can also use forAll, which allows you to supply a generator and a function over the generated value to produce a property. It would look like this:

mconcatP = forAll nonEmptyList $ \as -> ...

Upvotes: 1

Related Questions