Haskell 高阶类型的任意实例化

3

我有这个类型并且它是SemiGroup的一个实例。 我想编写一个quickCheck方法来确保它是正确的。 如何创建这种类型的一个Arbitrary实例?

newtype Combine a b =
  Combine { unCombine :: a -> b }

instance Semigroup b => Semigroup (Combine a b) where
  x <> y = Combine $ \n -> unCombine x n <> unCombine y n

instance (Q.Arbitrary a, Num a) => Q.Arbitrary (Combine a b) where
  arbitrary = do
    a <- Q.arbitrary
    return $ Combine (\n -> Sum(n+1)) a
1个回答

4
最简单的解决方法是打开FlexibleInstances并编写以下内容:
instance (Q.Arbitrary a, Num a) => Q.Arbitrary (Combine a (Sum a)) where
  arbitrary = do
    a <- Q.arbitrary :: Q.Gen ()
    return $ Combine (\n -> Sum(n+1))

然而,这有点令人不满意:我观察到a完全没有被使用(因此必须手动给出类型签名!),并且函数的分布有点无聊,因为它返回函数(+1)(直到新类型包装)的概率为1。(也许你想要return $ Combine (\n -> Sum (n+a))?但即使这样,你可以通过这种方式生成的函数类也有点无聊。我不确定你真正想表达什么,我不会花费太长时间进行推测。)
如果你想做得正确,你应该为每个输入产生随机输出。这在universe包中非常方便:
import Data.Universe
import Data.Universe.Instances.Reverse
instance (Ord a, Finite a, Q.Arbitrary b) => Q.Arbitrary (Combine a b) where
  arbitrary = Combine <$> sequenceA (const Q.arbitrary)

作为一个附加利益,这不需要任何扩展。但是,你应该小心选择具有小域的输入类型——Combine Bool (Sum Int)应该没问题,甚至Combine Word8 (Sum Word8)也可以,但如果你尝试在像Combine Int (Sum Int)这样的类型上测试属性,你会后悔的!

这看起来就是我想要的。我对 Haskell 还很陌生,正在尝试弄清楚如何使用 stack 将这些依赖项添加到我的单个文件中,而不需要使用 yaml 或 cabal 文件。当我搞清楚了这个问题,我会标记您的答案并在验证其有效后点赞。感谢您详细的回答。 - mac10688
如果您没有使用栈文件或Cabal文件,则需要使用 cabal install 命令安装软件包。您需要使用栈文件或Cabal文件以明确声明依赖项。 - David Young
@DavidYoung 我可以使用 stack ghci --resolver nightly-2015-11-10 --package universe QuickCheck。现在,我正在处理一些编译错误。 - mac10688
花了一些时间,但我终于证明你是对的了。我确实需要为Sum创建一个Arbitrary实例,并且我的Combine新类型需要派生出Eq和Show。 - mac10688

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接