I'm reading "Haskell Book"'s monoid section right now where author uses QuickCheck to check monoid laws. After writing some things on my own, I have this code:
module Main where
import Data.Monoid
import Test.QuickCheck
monoidAssoc :: (Eq m, Monoid m) => m -> m -> m -> Bool
monoidAssoc a b c = ((a <> b) <> c) == (a <> (b <> c))
monoidLeftId :: (Eq m, Monoid m) => m -> Bool
monoidLeftId a = (mempty <> a) == a
monoidRightId :: (Eq m, Monoid m) => m -> Bool
monoidRightId a = (a <> mempty) == a
type AssocCheck a = a -> a -> a -> Bool
type IdCheck a = a -> Bool
main :: IO ()
main = do
quickCheck (monoidAssoc :: AssocCheck (Maybe String))
quickCheck (monoidLeftId :: IdCheck (Maybe String))
quickCheck (monoidRightId :: IdCheck (Maybe String))
quickCheck (monoidAssoc :: AssocCheck [String])
quickCheck (monoidLeftId :: IdCheck [String])
quickCheck (monoidRightId :: IdCheck [String])
As you can see main
function has two almost same blocks which I would like to reduce to something like this:
checkMonoid :: (Eq m, Monoid m) => m -> IO ()
checkMonoid = do
quickCheck (monoidAssoc :: AssocCheck m)
quickCheck (monoidLeftId :: IdCheck m)
quickCheck (monoidRightId :: IdCheck m)
main :: IO ()
main = do
checkMonoid :: Maybe String -> IO ()
checkMonoid :: [String] -> IO ()
But this obviously will not work. What I want here is to somehow pass type to the checkMonoid
function so quickCheck
function will know what arbitrary
data must be generated.
This is exactly the sort of thing TypeApplication
lets you do - you can explicitly pass the type for m
. You then also need ScopedTypeVariables
to make sure that all the m
s inside you checkMonoid
are the same, and AllowAmbiguousTypes
to let GHC know that you are fine with the fact that checkMonoid
will be ambiguous without a type application.
{-# LANGUAGE TypeApplications, AllowAmbiguousTypes, ScopedTypeVariables #-}
-- ...
checkMonoid :: forall m. (Eq m, Monoid m, Show m, Arbitrary m) => IO ()
checkMonoid = do
quickCheck (monoidAssoc :: AssocCheck m)
quickCheck (monoidLeftId :: IdCheck m)
quickCheck (monoidRightId :: IdCheck m)
main :: IO ()
main = do
checkMonoid @(Maybe String)
checkMonoid @[String]
The principal behind TypeApplications
is that under the hood Haskell is transforming polymorphic functions into functions that also take types as arguments ( see more here ) - and normally GHC takes care of figuring out which type arguments to fill in. With TypeApplications
, @SomeType
says "let the first type argument to the preceding function call be SomeType
".
By having the forall m.
I make sure GHC will do the above. Then, when I call checkMonoid
, I pass the type for m
explicitly.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.