我开始做99个haskell问题,当时我在problem 7上,我的单元测试正在爆炸。
显然是由于以下原因:http://www.haskell.org/haskellwiki/Monomorphism_restriction
我只是想确保我正确理解了这一点,因为我有点困惑。
情况1:func a
是在没有类型def或非严格类型def的情况下定义的,然后使用一次,则编译器在编译时无法推断类型。
情况2:相同的func a
在程序中多次使用,编译器无法100%确定类型是什么,除非重新为给定参数重新计算函数。
为了避免计算损失,ghc向程序员抱怨说,它需要对a
进行严格的类型定义
正常工作。
我认为在我的情况下,assertEqual
具有类型def
assertEqual :: (Eq a, Show a) => String -> a -> a -> Assertion
我在定义
test3
时遇到错误,我认为这是因为它有两种可能的testcase3
返回类型(显示和等式),并且不知道如何继续。这听起来是否正确,还是我完全离开了?
问题7:
-- # Problem 7
-- Flatten a nested list structure.
import Test.HUnit
-- Solution
data NestedList a = Elem a | List [NestedList a]
flatten :: NestedList a -> [a]
flatten (Elem x) = [x]
flatten (List x) = concatMap flatten x
-- Tests
testcase1 = flatten (Elem 5)
assertion1 = [5]
testcase2 = flatten (List [Elem 1, List [Elem 2, List [Elem 3, Elem 4], Elem 5]])
assertion2 = [1,2,3,4,5]
-- This explodes
-- testcase3 = flatten (List [])
-- so does this:
-- testcase3' = flatten (List []) :: Eq a => [a]
-- this does not
testcase3'' = flatten (List []) :: Num a => [a]
-- type def based off `:t assertEqual`
assertEmptyList :: (Eq a, Show a) => String -> [a] -> Assertion
assertEmptyList str xs = assertEqual str xs []
test1 = TestCase $ assertEqual "" testcase1 assertion1
test2 = TestCase $ assertEqual "" testcase2 assertion2
test3 = TestCase $ assertEmptyList "" testcase3''
tests = TestList [test1, test2, test3]
-- Main
main = runTestTT tests
第一种情况:
testcase3 = flatten (List [])
GHCi, version 7.4.2: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main ( problem7.hs, interpreted )
problem7.hs:29:20:
Ambiguous type variable `a0' in the constraints:
(Eq a0)
arising from a use of `assertEmptyList' at problem7.hs:29:20-34
(Show a0)
arising from a use of `assertEmptyList' at problem7.hs:29:20-34
Probable fix: add a type signature that fixes these type variable(s)
In the second argument of `($)', namely
`assertEmptyList "" testcase3'
In the expression: TestCase $ assertEmptyList "" testcase3
In an equation for `test3':
test3 = TestCase $ assertEmptyList "" testcase3
Failed, modules loaded: none.
Prelude>
第二种情况:
testcase3 = flatten (List []) :: Eq a => [a]
GHCi, version 7.4.2: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main ( problem7.hs, interpreted )
problem7.hs:22:13:
Ambiguous type variable `a0' in the constraints:
(Eq a0)
arising from an expression type signature at problem7.hs:22:13-44
(Show a0)
arising from a use of `assertEmptyList' at problem7.hs:29:20-34
Possible cause: the monomorphism restriction applied to the following:
testcase3 :: [a0] (bound at problem7.hs:22:1)
Probable fix: give these definition(s) an explicit type signature
or use -XNoMonomorphismRestriction
In the expression: flatten (List []) :: Eq a => [a]
In an equation for `testcase3':
testcase3 = flatten (List []) :: Eq a => [a]
Failed, modules loaded: none.
最佳答案
并不是单态性限制,而是defaulting对类型不明确的变量的解析会导致编译失败。
-- This explodes
-- testcase3 = flatten (List [])
-- so does this:
-- testcase3' = flatten (List []) :: Eq a => [a]
-- this does not
testcase3'' = flatten (List []) :: Num a => [a]
flatten :: NestedList a -> [a]
flatten (Elem x) = [x]
flatten (List x) = concatMap flatten x
flatten
对类型变量a
没有任何限制,因此testcase3
的定义没有问题,因为它是多态的。但是当您在
test3
中使用它时,test3 = TestCase $ assertEmptyList "" testcase3 -- ''
您继承了
assertEmptyList :: (Eq a, Show a) => String -> [a] -> Assertion
现在,编译器必须找出在那里应该使用哪种类型的
testcase3
。没有足够的上下文来确定类型,因此编译器尝试通过默认解析类型变量。根据defaulting rules,默认情况下无法解析上下文(Eq a, Show a)
,因为只有涉及至少一个数字类的上下文才有资格进行默认设置。因此,由于类型变量不明确,导致编译失败。但是,由于表达式类型签名将
testcase3'
和testcase3''
置于单态性限制之下,因此表达式类型签名在定义的右侧施加了由左侧继承的约束。因此,不管
testcase3'
是否在断言中使用,它都无法编译。testcase3''
默认为[Integer]
,因为表达式类型签名强加了数字约束。因此,当类型针对testcase''
进行单态化时,受约束的类型变量默认为Integer
。毫无疑问,它在test3
中使用的类型。如果您将类型签名赋予了绑定(bind)而不是右侧,
testcase3' :: Eq a => [a]
testcase3' = flatten (List [])
testcase3'' :: Num a => [a]
testcase3'' = flatten (List [])
这两个值都可以自己编译为多态值,但是在
testcase3''
中仍然只能使用test3
,因为只有这样才能引入所需的数值约束以允许默认设置。