如何修复类列表类型List的Arbitrary实例歧义问题
咱们直接定位问题根源:你的genList里那行gen <- arbitrary完全是多余的,而且正是它导致了类型歧义。
为什么会出现歧义?
先看arbitrary的类型签名:
arbitrary :: Arbitrary b => Gen b
当你写gen <- arbitrary时,Haskell的类型推断器会认为你要取一个Gen a类型的值(因为后面你把gen传给了vectorOf n gen),这就要求Arbitrary (Gen a)必须成立——但根本不存在这个实例!你真正需要的是生成a类型的随机元素,vectorOf的第二个参数直接传arbitrary就够了,它本身就是Gen a类型(只要Arbitrary a约束满足)。
为什么main的类型断言解决不了?
main里的Nil :: List (Int, String, Int)是给整个List指定了具体类型,但genList是一个多态函数,它的类型问题出在自身定义内部,和调用时的类型注解无关。类型推断器在处理genList的时候,还不知道后面会用它生成什么具体类型的List,所以main的注解影响不到genList内部的类型推导。
修复后的genList代码
把多余的gen <- arbitrary删掉,直接用vectorOf n arbitrary即可:
genList :: Arbitrary a => Gen (List a) genList = do n <- choose (3 :: Int, 5) elems <- vectorOf n arbitrary -- 直接用arbitrary,不需要先绑定gen return $ build elems where build [] = Nil build (e:es) = Cons e (build es)
这样修改后,vectorOf n arbitrary的类型就是Gen [a],正好符合后续build函数的输入需求,类型歧义就彻底消除了。
另外你提到的genList = Cons <$> arbitrary <*> arbitrary是另一种写法,它会生成恰好包含两个元素的List,而修复后的原代码则会生成3到5个元素的List,两者语义不同,按需选择即可。
内容的提问来源于stack exchange,提问作者Jan Synáček

