Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MonadAccum law tests #125

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
ghc-version: ${{ matrix.ghc }}
cabal-version: 'latest'
- name: Configure
run: cabal new-configure
run: cabal new-configure --enable-tests
- name: Freeze
run: cabal freeze
- name: Cache
Expand All @@ -35,3 +35,5 @@ jobs:
restore-keys: ${{ runner.os }}-${{ matrix.ghc }}-
- name: Build
run: cabal build
- name: Run tests
run: cabal test
2 changes: 1 addition & 1 deletion Control/Monad/Accum.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ import Data.Kind (Type)
-- These are also the default definitions.
--
-- 1. @'look'@ @=@ @'accum' '$' \acc -> (acc, mempty)@
-- 2. @'add' x@ @=@ @'accum' '$' \acc -> ('()', x)@
-- 2. @'add' x@ @=@ @'accum' '$' \_ -> ('()', x)@
-- 3. @'accum' f@ @=@ @'look' >>= \acc -> let (res, v) = f acc in 'add' v '$>' res@
--
-- @since 2.3
Expand Down
2 changes: 2 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
packages: ./mtl.cabal

test-show-details: direct

package mtl
ghc-options: -Werror
32 changes: 23 additions & 9 deletions mtl.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,18 @@ source-repository head
type: git
location: https://github.com/haskell/mtl.git

common common-lang
build-depends:
, base >= 4.12 && < 5
, transformers >= 0.5.6 && < 0.7
ghc-options:
-Wall -Wcompat -Wincomplete-record-updates
-Wincomplete-uni-patterns -Wredundant-constraints
-Wmissing-export-lists
default-language: Haskell2010

Library
import: common-lang
exposed-modules:
Control.Monad.Cont
Control.Monad.Cont.Class
Expand All @@ -55,14 +66,17 @@ Library
Control.Monad.Accum
Control.Monad.Select

test-suite properties
import: common-lang
type: exitcode-stdio-1.0
main-is: Main.hs
other-modules: Accum
build-depends:
, base >=4.12 && < 5
, transformers >= 0.5.6 && <0.7

ghc-options:
-Wall -Wcompat -Wincomplete-record-updates
-Wincomplete-uni-patterns -Wredundant-constraints
-Wmissing-export-lists

default-language: Haskell2010
, mtl
, QuickCheck ^>= 2.14.0
, tasty ^>= 1.4.0.0
, tasty-quickcheck ^>= 0.10.0
, pretty-show ^>= 1.10

hs-source-dirs: test/properties
ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N
307 changes: 307 additions & 0 deletions test/properties/Accum.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}

module Accum
( M (..),
N (..),
AccumArb (..),
accumLaws,
accumLawsCont,
accumLawsSelect,
)
where

import Control.Monad (guard)
import Control.Monad.Accum (MonadAccum (accum, add, look))
import Data.Functor (($>))
import Data.Kind (Type)
import Test.QuickCheck
( Arbitrary (arbitrary, shrink),
Blind (Blind),
CoArbitrary (coarbitrary),
Property,
chooseInt,
forAllShrinkShow,
property,
shrinkList,
sized,
(===),
)
import Test.QuickCheck.Poly (A, B)
import Test.Tasty (TestTree)
import Test.Tasty.QuickCheck (testProperties)
import Text.Show.Pretty (ppShow)
import Type.Reflection
( Typeable,
tyConModule,
tyConName,
typeRep,
typeRepTyCon,
)

newtype M = M [Int]
deriving (Eq, Semigroup, Monoid) via [Int]
deriving stock (Show)

instance Arbitrary M where
arbitrary = M . pure <$> sized (\size -> chooseInt (0, abs size))
shrink (M xs) =
M <$> do
xs' <- shrinkList (const []) xs
guard (not . null $ xs')
pure xs'

instance CoArbitrary M where
coarbitrary (M xs) = coarbitrary xs

newtype N = N M
deriving (Eq, Semigroup, Monoid, Arbitrary) via M
deriving stock (Show)

newtype AccumArb (w :: Type) (a :: Type)
= AccumArb (w -> (a, w))
deriving (Arbitrary) via (w -> (a, w))

runAccumArb :: AccumArb w a -> w -> (a, w)
runAccumArb (AccumArb f) = f

accumLawsSelect ::
forall (m :: Type -> Type) (t :: Type).
(MonadAccum M m, Typeable m, Arbitrary t, Show t) =>
(forall (a :: Type). t -> m a -> (a -> AccumArb M B) -> AccumArb M a) ->
TestTree
accumLawsSelect lowerSelect =
testProperties
testName
[ ("look *> look = look", lookLookProp),
("add mempty = pure ()", addMemptyProp),
("add x *> add y = add (x <> y)", addAddProp),
("add x *> look = look >>= \\w -> add x $> w <> x", addLookProp),
("accum (const (x, mempty)) = pure x", accumPureProp),
("accum f *> accum g law (too long)", accumFGProp),
("look = accum $ \\acc -> (acc, mempty)", lookAccumProp),
("add x = accum $ \\acc -> ((), x)", addAccumProp),
("accum f = look >>= \\acc -> let (res, v) = f acc in add v $> res", accumAddProp)
]
where
testName :: String
testName = "MonadAccum laws for " <> typeName @(m A)
addAccumProp :: Property
addAccumProp = theNeedful $ \(w, arg, x, Blind f) ->
let lhs = lowerSelect arg (add x) f
rhs = lowerSelect arg (accum $ const ((), x)) f
in runAccumArb lhs w === runAccumArb rhs w
accumAddProp :: Property
accumAddProp = theNeedful $ \(w, arg, Blind (f :: M -> (A, M)), Blind g) ->
let lhs = lowerSelect arg (accum f) g
rhs = lowerSelect arg (look >>= \acc -> let (res, v) = f acc in add v $> res) g
in runAccumArb lhs w === runAccumArb rhs w
lookAccumProp :: Property
lookAccumProp = theNeedful $ \(w, arg, Blind f) ->
let lhs = lowerSelect arg look f
rhs = lowerSelect arg (accum (,mempty)) f
in runAccumArb lhs w === runAccumArb rhs w
lookLookProp :: Property
lookLookProp = theNeedful $ \(w, arg, Blind f) ->
let lhs = lowerSelect arg look f
rhs = lowerSelect arg (look *> look) f
in runAccumArb lhs w === runAccumArb rhs w
addMemptyProp :: Property
addMemptyProp = theNeedful $ \(w, arg, Blind f) ->
let lhs = lowerSelect arg (add mempty) f
rhs = lowerSelect arg (pure ()) f
in runAccumArb lhs w === runAccumArb rhs w
addAddProp :: Property
addAddProp = theNeedful $ \(w, arg, x, y, Blind f) ->
let lhs = lowerSelect arg (add x *> add y) f
rhs = lowerSelect arg (add (x <> y)) f
in runAccumArb lhs w === runAccumArb rhs w
addLookProp :: Property
addLookProp = theNeedful $ \(w, arg, x, Blind f) ->
let lhs = lowerSelect arg (add x *> look) f
rhs = lowerSelect arg (look >>= \w' -> add x $> w' <> x) f
in runAccumArb lhs w === runAccumArb rhs w
accumPureProp :: Property
accumPureProp = theNeedful $ \(w, arg, x :: A, Blind f) ->
let lhs = lowerSelect arg (accum (const (x, mempty))) f
rhs = lowerSelect arg (pure x) f
in runAccumArb lhs w === runAccumArb rhs w
accumFGProp :: Property
accumFGProp = theNeedful $ \(w', arg, Blind (f :: M -> (A, M)), Blind (g :: M -> (M, M)), Blind h) ->
let lhs = lowerSelect arg (accum f *> accum g) h
rhs =
lowerSelect
arg
( accum $ \acc ->
let (_, v) = f acc
(res, w) = g (acc <> v)
in (res, v <> w)
)
h
in runAccumArb lhs w' === runAccumArb rhs w'

accumLawsCont ::
forall (m :: Type -> Type) (t :: Type).
(MonadAccum M m, Typeable m, Arbitrary t, Show t) =>
(forall (a :: Type). t -> m a -> (a -> AccumArb M B) -> AccumArb M B) ->
TestTree
accumLawsCont lowerCont =
testProperties
testName
[ ("look *> look = look", lookLookProp),
("add mempty = pure ()", addMemptyProp),
("add x *> add y = add (x <> y)", addAddProp),
("add x *> look = look >>= \\w -> add x $> w <> x", addLookProp),
("accum (const (x, mempty)) = pure x", accumPureProp),
("accum f *> accum g law (too long)", accumFGProp),
("look = accum $ \\acc -> (acc, mempty)", lookAccumProp),
("add x = accum $ \\acc -> ((), x)", addAccumProp),
("accum f = look >>= \\acc -> let (res, v) = f acc in add v $> res", accumAddProp)
]
where
testName :: String
testName = "MonadAccum laws for " <> typeName @(m A)
addAccumProp :: Property
addAccumProp = theNeedful $ \(w, arg, x, Blind f) ->
let lhs = lowerCont arg (add x) f
rhs = lowerCont arg (accum $ const ((), x)) f
in runAccumArb lhs w === runAccumArb rhs w
accumAddProp :: Property
accumAddProp = theNeedful $ \(w, arg, Blind (f :: M -> (A, M)), Blind g) ->
let lhs = lowerCont arg (accum f) g
rhs = lowerCont arg (look >>= \acc -> let (res, v) = f acc in add v $> res) g
in runAccumArb lhs w === runAccumArb rhs w
lookAccumProp :: Property
lookAccumProp = theNeedful $ \(w, arg, Blind f) ->
let lhs = lowerCont arg look f
rhs = lowerCont arg (accum (,mempty)) f
in runAccumArb lhs w === runAccumArb rhs w
lookLookProp :: Property
lookLookProp = theNeedful $ \(w, arg, Blind f) ->
let lhs = lowerCont arg look f
rhs = lowerCont arg (look *> look) f
in runAccumArb lhs w === runAccumArb rhs w
addMemptyProp :: Property
addMemptyProp = theNeedful $ \(w, arg, Blind f) ->
let lhs = lowerCont arg (add mempty) f
rhs = lowerCont arg (pure ()) f
in runAccumArb lhs w === runAccumArb rhs w
addAddProp :: Property
addAddProp = theNeedful $ \(w, arg, x, y, Blind f) ->
let lhs = lowerCont arg (add x *> add y) f
rhs = lowerCont arg (add (x <> y)) f
in runAccumArb lhs w === runAccumArb rhs w
addLookProp :: Property
addLookProp = theNeedful $ \(w, arg, x, Blind f) ->
let lhs = lowerCont arg (add x *> look) f
rhs = lowerCont arg (look >>= \w' -> add x $> w' <> x) f
in runAccumArb lhs w === runAccumArb rhs w
accumPureProp :: Property
accumPureProp = theNeedful $ \(w, arg, x :: A, Blind f) ->
let lhs = lowerCont arg (accum (const (x, mempty))) f
rhs = lowerCont arg (pure x) f
in runAccumArb lhs w === runAccumArb rhs w
accumFGProp :: Property
accumFGProp = theNeedful $ \(w', arg, Blind (f :: M -> (A, M)), Blind (g :: M -> (M, M)), Blind h) ->
let lhs = lowerCont arg (accum f *> accum g) h
rhs =
lowerCont
arg
( accum $ \acc ->
let (_, v) = f acc
(res, w) = g (acc <> v)
in (res, v <> w)
)
h
in runAccumArb lhs w' === runAccumArb rhs w'

accumLaws ::
forall (m :: Type -> Type) (t :: Type).
(MonadAccum M m, Typeable m, Arbitrary t, Show t) =>
(forall (a :: Type). (Eq a) => t -> m a -> m a -> Bool) ->
TestTree
accumLaws runAndCompare =
testProperties
testName
[ ("look *> look = look", lookLookProp),
("add mempty = pure ()", addMemptyProp),
("add x *> add y = add (x <> y)", addAddProp),
("add x *> look = look >>= \\w -> add x $> w <> x", addLookProp),
("accum (const (x, mempty)) = pure x", accumPureProp),
("accum f *> accum g law (too long)", accumFGProp),
("look = accum $ \\acc -> (acc, mempty)", lookAccumProp),
("add x = accum $ \\acc -> ((), x)", addAccumProp),
("accum f = look >>= \\acc -> let (res, v) = f acc in add v $> res", accumAddProp)
]
where
testName :: String
testName = "MonadAccum laws for " <> typeName @(m A)
addAccumProp :: Property
addAccumProp = theNeedful $ \(w, x) ->
let lhs = add x
rhs = accum $ const ((), x)
in property . runAndCompare w lhs $ rhs
accumAddProp :: Property
accumAddProp = theNeedful $ \(w, Blind (f :: M -> (A, M))) ->
let lhs = accum f
rhs = look >>= \acc -> let (res, v) = f acc in add v $> res
in property . runAndCompare w lhs $ rhs
lookLookProp :: Property
lookLookProp = theNeedful $ \w ->
let lhs = look *> look
rhs = look
in property . runAndCompare w lhs $ rhs
addMemptyProp :: Property
addMemptyProp = theNeedful $ \w ->
let lhs = add mempty
rhs = pure ()
in property . runAndCompare w lhs $ rhs
addAddProp :: Property
addAddProp = theNeedful $ \(w, x, y) ->
let lhs = add x *> add y
rhs = add (x <> y)
in property . runAndCompare w lhs $ rhs
addLookProp :: Property
addLookProp = theNeedful $ \(w, x) ->
let lhs = add x *> look
rhs = look >>= \w' -> add x $> w' <> x
in property . runAndCompare w lhs $ rhs
accumPureProp :: Property
accumPureProp = theNeedful $ \(w, x :: A) ->
let lhs = accum (const (x, mempty))
rhs = pure x
in property . runAndCompare w lhs $ rhs
accumFGProp :: Property
accumFGProp = theNeedful $ \(w', Blind (f :: M -> (A, M)), Blind (g :: M -> (M, M))) ->
let lhs = accum f *> accum g
rhs = accum $ \acc ->
let (_, v) = f acc
(res, w) = g (acc <> v)
in (res, v <> w)
in property . runAndCompare w' lhs $ rhs
lookAccumProp :: Property
lookAccumProp = theNeedful $ \w ->
let lhs = look
rhs = accum (,mempty)
in property . runAndCompare w lhs $ rhs

-- Helpers

typeName :: forall (a :: Type). (Typeable a) => String
typeName =
let ourTyCon = typeRepTyCon $ typeRep @a
in tyConModule ourTyCon <> "." <> tyConName ourTyCon

theNeedful ::
forall (a :: Type).
(Arbitrary a, Show a) =>
(a -> Property) ->
Property
theNeedful = forAllShrinkShow arbitrary shrink ppShow
Loading