如何在参数化类型上编写“Semigroup”实例及其“quickCheck”?

How to write `Semigroup` instance and their `quickCheck`s on parameterized types?

在关于 Semigroup 的 Haskell Programming from First Principle 这本书的练习中,我被要求为用户定义的类型类编写 quickCheck。类型类很多,但我连最基本的都不知道怎么写:

问题:

第一个是 Trivial:

module Exercise where

import Test.QuickCheck

data Trivial =
  Trivial
  deriving (Eq, Show)

instance Semigroup Trivial where
  _ <> _ = undefined

instance Arbitrary Trivial where
  arbitrary = return Trivial

semigroupAssoc :: (Eq m, Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c = (a <> (b <> c)) == ((a <> b) <> c)

type TrivialAssoc = Trivial -> Trivial -> Trivial -> Bool

第二个是

newtype Identity a = Identity a

第三个用于:

data Two a b =
  Two a b

我的答案:

首先,我将 instance 表达式更改为

instance Semigroup Trivial where
  _ <> _ = Trivial

而且有效。

我尝试了以下代码,但第二个代码不起作用:

newtype Identity a = Identity a

instance (Semigroup a) => Semigroup (Identity a) where
  (Identity a1) <> (Identity a2) = Identity (a1 <> a2)

instance Arbitrary (Identity a) where
  arbitrary = return (Identity a)

type IdentityAssoc =
  (Identity a0) -> (Identity a1) -> (Identity a2) -> Bool

main :: IO ()
main =
  quickCheck (semigroupAssoc :: IdentityAssoc)

我发现我不明白 quickTest 应该检查什么。我什至试过:

import Data.NonEmpty

newtype Identity a = Identity a

instance (Semigroup a) => Semigroup (Identity a) where
  (Identity a1) <> (Identity a2) = Identity (a1 <> a2)

instance Arbitrary (Identity a) where
  arbitrary = return (Identity a)

type IdentityAssoc =
  (Identity (NonEmpty Int)) -> (Identity (NonEmpty Int)) -> (Identity (NonEmpty Int)) -> Bool

main :: IO ()
main =
  quickCheck (semigroupAssoc :: IdentityAssoc)

使参数化类型的参数具体化。但是也没用。

第三个,不知道怎么写。不过我觉得和第二个差不多

有人可以解释一下这些,以便我可以理解如何编写参数化半群的 instance 及其 quickTest 任意值吗?

这是错误的:

instance Arbitrary (Identity a) where
  arbitrary = return (Identity a)

a不是值变量,是类型变量。我们需要 a 类型的值传递给 Identity 构造函数,而不是 a 类型本身。

所以我们需要像

这样的东西
instance Arbitrary a => Arbitrary (Identity a) where
  arbitrary = do
     x <- arbitrary         -- generate a value of type a
     return (Identity x)    -- turn it into a value of type (Identity a)

(或者,更简洁地说,arbitrary = Identity <$> arbitrary

请注意我们如何要求 a 是我们可以为其生成随机样本的类型(在 Instance 之后添加 Arbitrary a =>)。否则,我们无法使用 x <- arbitrarya.

生成样本

进一步:

type IdentityAssoc =
  (Identity a0) -> (Identity a1) -> (Identity a2) -> Bool

这里我们不能引用 a1,a1,a2,因为我们没有在任何地方定义这些类型。我们需要选择具体的类型,比如 Int。此外,这三种类型必须是同一类型,因为 (<>) 取两个相同类型的值,而 returns 取该类型的一个值。