packages feed

smallcheck-laws (empty) → 0.1

raw patch · 15 files changed

+614/−0 lines, 15 filesdep +basedep +smallcheckdep +smallcheck-lawssetup-changed

Dependencies added: base, smallcheck, smallcheck-laws, smallcheck-series, tagged, tasty, tasty-smallcheck, transformers

Files

+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Change Log+All notable changes to this project will be documented in this file. This file+follows the formatting recommendations from [Keep a+CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic+Versioning](http://semver.org/).++## [0.1]+### Added+- Monoid Laws.++[0.1]: https://github.com/jdnavarro/smallcheck-laws/compare/ROOT...v0.1
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, J. Daniel Navarro++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of J. Daniel Navarro nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,26 @@+# SmallCheck Laws++[![Hackage Version](https://img.shields.io/hackage/v/smallcheck-laws.svg)](https://hackage.haskell.org/package/smallcheck-laws) [![Build Status](https://img.shields.io/travis/jdnavarro/smallcheck-laws.svg)](https://travis-ci.org/jdnavarro/smallcheck-laws)++Automatic `smallcheck` properties and `tasty` runners for:++ - Monoid laws.+ - Functor laws.+ - Applicative laws.+ - Monad laws.++## Installation++Make sure you have [`stack`](https://github.com/commercialhaskell/stack)+installed. Then type:++```sh+$ stack build+```+To run the test suite type:++```sh+$ stack test+```++<!-- TODO: Write minitutorial here about to use 'tasty' orphan instances -->
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Test/SmallCheck/Laws/Applicative.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Test.SmallCheck.Laws.Applicative+  (+  -- * Applicative laws+    identity+  , composition+  , homomorphism+  , interchange+  ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (Applicative, (<$>), (<*>), pure)+#endif+import Data.Proxy (Proxy)+import Data.Functor.Identity (Identity)+import Test.SmallCheck (Property, over)+import Test.SmallCheck.Series (Serial, Series)+import Test.SmallCheck.Series.Utils (zipLogic, zipLogic3)++-- | Check the /identity/ law hold for the given 'Applicative' 'Series':+--+-- @+-- 'pure' id '<*>' v ≡ v+-- @+identity+  :: (Eq (f a), Monad m, Show (f a), Applicative f)+  => Series m (f a) -> Property m+identity xs = over xs $ \x -> (pure id <*> x) == x++-- | Check the /composition/ law hold for the given 'Applicative' 'Series':+--+-- @+-- '(.)' '<$>' u '<*>' v '<*>' w ≡  u '<*>' (v '<*>' w)+-- @+composition+  :: ( Eq (f b)+     , Monad m+     , Show (f c)+     , Show (f (a -> b))+     , Show (f (c -> a))+     , Applicative f+     )+  => Series m (f (c -> a))+  -> Series m (f c)+  -> Series m (f (a -> b))+  -> Property m+composition vs ws us = over (zipLogic3 vs ws us) $ \(v,w,u) ->+    (pure (.) <*> u <*> v <*> w) == (u <*> (v <*> w))++-- | Check the /homomorphism/ law hold for the given 'Applicative' 'Series':+--+-- @+-- 'pure' f '<*>' 'pure' x ≡ 'pure' (f x)+-- @+homomorphism+  :: forall m f a b .+     ( Monad m+     , Applicative f+     , Eq b+     , Eq (f b)+     , Show a, Show b+     , Serial Identity a+     , Serial Identity b+     )+  => Proxy f -> Series m a -> Series m (a -> b) -> Property m+homomorphism _ xs fs = over (zipLogic xs fs) $ \(x,f) ->+    (pure f <*> (pure x :: f a)) == pure (f x)++-- | Check the /interchange/ law hold for the given 'Applicative' 'Series':+--+-- @+-- u '<*>' 'pure' y ≡ 'pure' ($ y) '<*>' u+-- @+interchange+  :: ( Eq (f b)+     , Monad m+     , Show a+     , Show (f (a -> b))+     , Applicative f+     )+  => Series m a -> Series m (f (a -> b)) -> Property m+interchange ys us = over (zipLogic ys us) $ \(y,u) ->+    (u <*> pure y) == (pure ($ y) <*> u)
+ Test/SmallCheck/Laws/Functor.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE FlexibleContexts #-}+module Test.SmallCheck.Laws.Functor+  (+  -- * Functor laws+    identity+  , composition+  ) where++import Data.Functor.Identity (Identity)+import Test.SmallCheck (Property, over)+import Test.SmallCheck.Series (Serial, Series)+import Test.SmallCheck.Series.Utils (zipLogic3)++-- | Check the /identity/ law hold for the given 'Functor' 'Series':+--+-- @+-- 'fmap' 'id' ≡ 'id'+-- @+identity+  :: (Eq (f a), Monad m, Show (f a), Functor f)+  => Series m (f a) -> Property m+identity s = over s $ \x -> fmap id x == x++-- | Check the /composition/ law hold for the given 'Functor' 'Series':+--+-- @+-- 'fmap' (f . g) ≡ 'fmap' f . 'fmap' g+-- @+composition+  :: ( Monad m, Functor f, Show a, Show b, Show c+     , Show (f a), Eq (f c)+     , Serial Identity a, Serial Identity b+     )+  => Series m (f a) -> Series m (b -> c) -> Series m (a -> b) -> Property m+composition xs fs gs = over (zipLogic3 xs fs gs) $ \(x,f,g) ->+    fmap (f . g) x == (fmap f . fmap g) x
+ Test/SmallCheck/Laws/Monad.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE FlexibleContexts #-}+{-|+Provided a 'Monad' is already an 'Applicative' there is no need to check the+first 2 monad laws. See+@<https://hackage.haskell.org/package/semigroupoids-5.0.0.2/docs/Data-Functor-Bind.html#t:Bind Bind>@+for which laws are exclusive for the '>>=' method.+-}+module Test.SmallCheck.Laws.Monad+  (+  -- * Monad laws+    associativity+  ) where++import Control.Monad ((>=>))+import Data.Functor.Identity (Identity)++import Test.SmallCheck (Property, over)+import Test.SmallCheck.Series (Serial, Series)+import Test.SmallCheck.Series.Utils (zipLogic3)++-- | Check the /associativity/ law hold for the given 'Monad' 'Series':+--+-- @+-- (m '>>=' f) '>>=' g ≡ m (f '>=>' g)+-- @+--+-- This is equivalent to:+--+-- @+-- (f '>=>' g) '>=>' h == f '>=>' (g '>=>' h)+-- @+--+-- Assuming 'join' is the default implementation of 'Monad'.++-- This is equivalent to `(f >=> g) >=> h == f >=> (g >=> h)` which requires+-- the constraint `Eq (a -> f b)`. `Eq (f a)` is much easier to deal with.+associativity+  :: ( Monad m, Monad f+     , Show a, Show b, Show c, Show (f a), Show (f b), Show (f c)+     , Eq (f a), Eq (f c)+     , Serial Identity a, Serial Identity b, Serial Identity c+     )+  => Series m (f a)+  -> Series m (a -> f b)+  -> Series m (b -> f c)+  -> Property m+associativity ms fs gs = over (zipLogic3 ms fs gs) $ \(m,f,g) ->+    (m >>= f >>= g) == (m >>= (f >=> g))
+ Test/SmallCheck/Laws/Monoid.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE CPP #-}+module Test.SmallCheck.Laws.Monoid+  (+  -- * Monoid laws+    leftIdentity+  , rightIdentity+  , associativity+  , mconcat+  ) where++#if MIN_VERSION_base(4,8,0)+import Prelude hiding (mconcat)+#else+import Data.Monoid (Monoid, mappend, mempty)+import Data.Traversable (sequenceA)+#endif+import Data.Monoid ((<>))+import qualified Data.Monoid as Monoid (mconcat)+import Test.SmallCheck (Property, over)+import Test.SmallCheck.Series (Series)+import Test.SmallCheck.Series.Utils (zipLogic3)++-- | Check the /left identity/ law hold for the given 'Monoid' 'Series':+--+-- @+-- 'mempty' '<>' x ≡ x+-- @+leftIdentity+  :: (Eq a, Monad m, Show a, Monoid a)+  => Series m a -> Property m+leftIdentity s = over s $ \x -> mempty <> x == x++-- | Check the /right identity/ law hold for the given 'Monoid' 'Series':+--+-- @+-- x '<>' 'mempty' ≡ x+-- @+rightIdentity+  :: (Eq a, Monad m, Show a, Monoid a)+  => Series m a -> Property m+rightIdentity s = over s $ \x -> x <> mempty == x++-- | Check the /associativity/ law hold for the given 'Monoid' 'Series':+--+-- @+-- x '<>' (y '<>' z) ≡ (x '<>' y) '<>' z+-- @+associativity+  :: (Eq a, Monad m, Show a, Monoid a)+  => Series m a -> Series m a -> Series m a -> Property m+associativity xs ys zs =+    over (zipLogic3 xs ys zs) $ \(x,y,z) ->+        x <> (y <> z) == (x <> y) <> z++-- | Check the /mconcat/ law hold for the given 'Monoid' 'Series':+--+-- @+-- 'mconcat' ≡ 'foldr' 'mappend' 'mempty'+-- @+mconcat+  :: (Eq a, Monad m, Show a, Monoid a)+  => Series m a -> Property m+mconcat s = over (sequenceA $ replicate 3 s) $ \l ->+    Monoid.mconcat l == foldr mappend mempty l
+ Test/Tasty/SmallCheck/Laws/Applicative.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Test.Tasty.SmallCheck.Laws.Applicative where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (Applicative)+#endif+import Data.Functor.Identity (Identity)+import Data.Proxy (Proxy(..))+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.SmallCheck (testProperty)+import Test.SmallCheck.Series (Series, Serial(series))++import qualified Test.SmallCheck.Laws.Applicative as Applicative+import Test.Tasty.SmallCheck.Laws.Functor++-- | @tasty@ 'TestTree' for 'Applicative' laws. You need to provide the type+--   wrapped in a `Proxy` and make sure 'a' is an instance of 'Serial'.+testApplicative+  :: forall f a .+     ( Applicative f+     , Show a, Eq a+     , Show (f a), Eq (f a), (Eq (f (f a)))+     , Show (f (a -> a))+     , Serial IO a+     , Serial IO (f a)+     , Serial IO (a -> a)+     , Serial IO (f (a -> a))+     , Serial Identity a, Serial Identity (f a)+     )+  => Proxy (f a) -> TestTree+testApplicative proxy = testGroup "Applicative"+  [ testFunctor proxy+  , testProperty "pure id <*> v ≡ v"+  $ Applicative.identity (series :: Series IO (f a))+  , testProperty "(.) <$> u <*> v <*> w ≡  u <*> (v <*> w)"+  $ Applicative.composition+      (series :: Series IO (f (a -> a)))+      (series :: Series IO (f a))+      (series :: Series IO (f (a -> a)))+  , testProperty "pure f <*> pure x ≡ pure (f x)" $ Applicative.homomorphism+      (Proxy :: Proxy f)+      (series :: Series IO a)+      (series :: Series IO (a -> a))+  , testProperty "u <*> pure y ≡ pure ($ y) <*> u" $ Applicative.interchange+      (series :: Series IO a)+      (series :: Series IO (f (a -> a)))+  ]
+ Test/Tasty/SmallCheck/Laws/Functor.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Test.Tasty.SmallCheck.Laws.Functor where++import Data.Proxy (Proxy)+import Data.Functor.Identity (Identity)+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.SmallCheck (testProperty)+import Test.SmallCheck.Series (Serial(series), Series)++import qualified Test.SmallCheck.Laws.Functor as Functor++-- | @tasty@ 'TestTree' for 'Functor' laws. You need to provide the type+--   wrapped in a `Proxy` and make sure 'a' is an instance of 'Serial'.+testFunctor+  :: forall f a .+     ( Eq (f a), Eq (f (f a)), Functor f, Show a, Show (f a)+     , Serial IO (f a)+     , Serial IO (a -> a)+     , Serial Identity a, Serial Identity (f a)+     )+  => Proxy (f a) -> TestTree+testFunctor _ = testGroup "Functor laws"+  [ testProperty "fmap id ≡ id" $ Functor.identity (series :: Series IO (f a))+  , testProperty "fmap (f . g) ≡ fmap f . fmap g" $ Functor.composition+      (series :: Series IO (f a))+      (series :: Series IO (a -> a))+      (series :: Series IO (a -> a))+  ]
+ Test/Tasty/SmallCheck/Laws/Monad.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Test.Tasty.SmallCheck.Laws.Monad where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (Applicative)+#endif+import Data.Functor.Identity (Identity)+import Data.Proxy (Proxy(..))+import Test.SmallCheck.Series (Series, Serial(series))+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.SmallCheck (testProperty)++import Test.Tasty.SmallCheck.Laws.Applicative+import qualified Test.SmallCheck.Laws.Monad as Monad++-- | @tasty@ 'TestTree' for 'Monad' laws. You need to provide the type+--   wrapped in a `Proxy` and make sure 'a' is an instance of 'Serial'.+testMonad+  :: forall f a .+     ( Applicative f, Monad f+     , Show a, Show (f a), Show (f (a -> a))+     , Eq a, Eq (f a), Eq (f (f a))+     , Serial IO a, Serial IO (a -> a)+     , Serial IO (f a) ,Serial IO (f (a -> a)), Serial IO (a -> f a)+     , Serial Identity a, Serial Identity (f a)+     )+  => Proxy (f a) -> TestTree+testMonad proxy = testGroup "Monad laws"+  [ testApplicative proxy+  , testProperty "(m >>= f) >>= g ≡ m (f >=> g)"+  $ Monad.associativity (series :: Series IO (f a))+                        (series :: Series IO (a -> f a))+                        (series :: Series IO (a -> f a))+  ]
+ Test/Tasty/SmallCheck/Laws/Monoid.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Test.Tasty.SmallCheck.Laws.Monoid where++#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (Monoid)+#endif+import Data.Proxy (Proxy)+import Test.SmallCheck.Series (Series, Serial(series))+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.SmallCheck (testProperty)++import qualified Test.SmallCheck.Laws.Monoid as Monoid++-- | @tasty@ 'TestTree' for 'Applicative' laws. You need to provide the type+--   wrapped in a `Proxy` and make sure 'a' is an instance of 'Serial'.+testMonoid :: forall a . (Show a, Eq a, Monoid a, Serial IO a) => Proxy a -> TestTree+testMonoid _ = testGroup "Monoid laws"+  [ testProperty "mempty <> x ≡ x" $ Monoid.leftIdentity (series :: Series IO a)+  , testProperty "x <> mempty ≡ x" $ Monoid.rightIdentity (series :: Series IO a)+  , testProperty "x <> (y <> z) ≡ (x <> y) <> z"+  $ Monoid.associativity (series :: Series IO a) series series+  , testProperty "mconcat ≡ foldr mappend mempty"+  $ Monoid.mconcat (series :: Series IO a)+  ]
+ smallcheck-laws.cabal view
@@ -0,0 +1,67 @@+name:                smallcheck-laws+version:             0.1+synopsis:            SmallCheck properties for standard type classes+description:+  Automatic `smallcheck` properties and `tasty` runners for:+  .+  - Monoid laws.+  - Functor laws.+  - Applicative laws.+  - Monad laws.+  .+  Use the @<https://hackage.haskell.org/package/tasty Tasty>@ modules for+  preassembled tasty runners, your data types need to be instances of+  @<https://hackage.haskell.org/package/smallcheck-1.1.1/docs/Test-SmallCheck-Series.html#t:Serial Serial>@.+  For more granular control on how the+  @<https://hackage.haskell.org/package/smallcheck SmallCheck>@+  @<https://hackage.haskell.org/package/smallcheck-1.1.1/docs/Test-SmallCheck-Series.html#t:Series Series>@+  are created use the modules under @Test.SmallCheck.Laws@.+license:             BSD3+license-file:        LICENSE+author:              Danny Navarro+maintainer:          j@dannynavarro.net+category:            Testing+build-type:          Simple+cabal-version:       >=1.10+extra-source-files:  README.md CHANGELOG.md+                     stack.yaml++source-repository head+  type: git+  location: git://github.com/jdnavarro/smallcheck-laws.git++library+  ghc-options:         -Wall+  default-language:    Haskell2010+  exposed-modules:     Test.SmallCheck.Laws.Applicative,+                       Test.SmallCheck.Laws.Functor,+                       Test.SmallCheck.Laws.Monad,+                       Test.SmallCheck.Laws.Monoid,+                       Test.Tasty.SmallCheck.Laws.Applicative,+                       Test.Tasty.SmallCheck.Laws.Functor,+                       Test.Tasty.SmallCheck.Laws.Monad,+                       Test.Tasty.SmallCheck.Laws.Monoid+  build-depends:       base >=4.6 && <4.9,+                       smallcheck >=1.1.1,+                       smallcheck-series >=0.3,+                       tasty >=0.10,+                       tasty-smallcheck >=0.8+  if impl(ghc < 7.10)+     build-depends: transformers >=0.3.0.0++  if impl(ghc < 7.8)+     build-depends: tagged >=0.7.2++test-suite tasty+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  hs-source-dirs:      tests+  main-is:             tasty.hs+  ghc-options:         -Wall -threaded+  build-depends:       base >=4.6 && <4.9,+                       smallcheck >=1.1,+                       tasty >=0.10,+                       smallcheck-laws++  if impl(ghc < 7.8)+     build-depends: tagged >=0.7.2
+ stack.yaml view
@@ -0,0 +1,6 @@+flags: {}+packages:+- '.'+extra-deps:+- smallcheck-series-0.3+resolver: lts-2.21
+ tests/tasty.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Main where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif+import Data.Monoid (Sum(..), Product(..))+import Data.Proxy (Proxy(..))++import Test.SmallCheck.Series (Serial(series))+import Test.Tasty (TestTree, defaultMain, testGroup)++import Test.Tasty.SmallCheck.Laws.Applicative+import Test.Tasty.SmallCheck.Laws.Functor+import Test.Tasty.SmallCheck.Laws.Monad+import Test.Tasty.SmallCheck.Laws.Monoid++main :: IO ()+main = defaultMain $ testGroup "Laws"+     [ monoidTests+     , functorTests+     , applicativeTests+     , monadTests+     ]++monoidTests :: TestTree+monoidTests = testGroup "Monoid"+  [ testGroup "Sum"+    [ testGroup "Int"+      [ testMonoid (Proxy :: Proxy (Sum Int)) ]+    , testGroup "Integer"+      [ testMonoid (Proxy :: Proxy (Sum Integer)) ]+    , testGroup "Float"+      [ testMonoid (Proxy :: Proxy (Sum Float)) ]+    ]+  , testGroup "Product"+     [ testGroup "Int"+      [ testMonoid (Proxy :: Proxy (Product Int)) ]+    , testGroup "Integer"+      [ testMonoid (Proxy :: Proxy (Product Integer)) ]+    , testGroup "Float"+      [ testMonoid (Proxy :: Proxy (Product Float)) ]+    ]+  ]++functorTests :: TestTree+functorTests = testGroup "Functor"+  [ testGroup "Maybe"+    [ testGroup "Int"+      [ testFunctor (Proxy :: Proxy (Maybe Int)) ]+    , testGroup "Char"+      [ testFunctor (Proxy :: Proxy (Maybe Char)) ]+    ]+  , testGroup "[]"+    [ testGroup "Bool"+      [ testFunctor (Proxy :: Proxy [Bool]) ]+    , testGroup "Int"+      [ testFunctor (Proxy :: Proxy [Int]) ]+    ]+  ]++applicativeTests :: TestTree+applicativeTests = testGroup "Applicative"+  [ testGroup "Maybe"+    [ testGroup "Int"+      [ testApplicative (Proxy :: Proxy (Maybe Int)) ]+    , testGroup "Float"+      [ testApplicative (Proxy :: Proxy (Maybe Float)) ]+    ]+  , testGroup "[]"+    [ testGroup "Bool"+      [ testApplicative (Proxy :: Proxy [Bool]) ]+    , testGroup "Char"+      [ testApplicative (Proxy :: Proxy [Char]) ]+    ]+  ]++monadTests :: TestTree+monadTests = testGroup "Monad"+  [ testGroup "Maybe"+    [ testGroup "()"+      [ testMonad (Proxy :: Proxy (Maybe ())) ]+    , testGroup "Int"+      [ testMonad (Proxy :: Proxy (Maybe Int)) ]+    ]+  , testGroup "[]"+    [ testGroup "()"+      [ testMonad (Proxy :: Proxy [()]) ]+    ]+  ]++instance (Monad m, Serial m a) => Serial m (Sum a) where+    series = Sum <$> series++instance (Monad m, Serial m a) => Serial m (Product a) where+    series = Product <$> series