简体   繁体   中英

How to define generic function without argument of generic type?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM