0

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?

maxloo
  • 453
  • 2
  • 12
  • 1
    Oh, uhh, this isn't going to make `monoid zl` work. That function uses its own version of `monoidP` internally, and you can't change that without changing the function itself. What you _can_ do is define your own version of `monoid`. Just copy the code into your project (perhaps renaming it, e.g., `monoidNonEmpty`), and redefine `monoidP` internal to this new version. – DDub Jan 24 '21 at 14:59
  • Sorry, I'm missing something here. Do I have to create a new file monoidNonEmpty.hs? My current code is in the file Testing.hs. So, I'll have to include `import monoidNonEmpty` in my Testing.hs? How do I redefine monoidP? Do I need to update my Cabal file? – maxloo Jan 24 '21 at 15:18
  • 1
    I'm not sure what wasn't clear about my statement. Literally: copy the code of `monoid` into your test module. Change the line that defines `monoidP` to your version of `monoidP`. Change your `quickBatch` line to call your new version of `monoid`. I mean, if you want, you can put this new version of `monoid` into a separate module (then, of course, you'd have to update your cabal file—also, module names must be capitalized), but that's up to you. – DDub Jan 24 '21 at 16:33
  • The problem I face is that there is no `monoidP` in `Data.Monoid`: https://hackage.haskell.org/package/base-4.14.1.0/docs/Data-Monoid.html.. There is also no `monoidP` in `Test.QuickCheck.Checkers`: https://hackage.haskell.org/package/checkers-0.5.6/docs/Test-QuickCheck-Checkers.html.. And if I am to copy the actual `Monoid` source code, will it start from https://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#Monoid? Where should it end? There's quite a lot of code there.. – maxloo Jan 24 '21 at 17:01
  • 1
    I'm talking about the function `monoid`: https://hackage.haskell.org/package/checkers-0.5.6/docs/src/Test.QuickCheck.Classes.html#monoid. This is the function you're calling, and this is what you need to copy and modify. Notice that it _internally_ defines a function `monoidP`—that is what you need to change in your local version. – DDub Jan 24 '21 at 17:13
  • Thanks for the link, but there's no `monoidP`, only `mconcatP`, and the method you propose threw up a lot of errors centred around the context: `(Monoid a, Show a, Arbitrary a, EqProp a)`, namely Monoid, Arbitrary and EqProp. I copied from the line `monoid :: forall a. ..` until the line `monoidSemigroupP x y ..`, and included my own code for `mconcatP`. I also removed the `#if` code. Am I right in doing these? – maxloo Jan 24 '21 at 18:27
  • 1
    You're right. It's called `mconcatP`. Yes, you can remove the lines that start with `#`, but you should keep in the `mappend` test and the definition of `monoidSemigroupP `. The type for `mconcatP` should be `NonEmptyList a -> Property`. You may need to enable `ScopedTypeVariables` by adding `{-# LANGUAGE ScopedTypeVariables #-}` to the top of your file so that the `a` type properly scopes. – DDub Jan 24 '21 at 18:49

1 Answers1

1

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 -> ...
DDub
  • 3,884
  • 1
  • 5
  • 12
  • Upon double checking, it looks like the constructor for `NonEmptyList` is just `NonEmpty`. I've updated my answer to reflect that. – DDub Jan 24 '21 at 14:53
  • Thanks, I've updated my post. I've also tried `mconcatP (NonEmptyList as)`, but an error was thrown. I've also tried `mconcatP (getNonEmpty -> as)` because an error was thrown when I use getNonEmptyList, but the outcome was stack overflow at the mconcat test again. – maxloo Jan 24 '21 at 14:55