2012-07-29 104 views
8

我開始做99個哈斯克爾問題,我在problem 7,我的單位測試結果爆炸了。請向我解釋單態限制嗎?

顯然,這是由於這樣的:http://www.haskell.org/haskellwiki/Monomorphism_restriction

我只是想確保我正確地理解這一點,因爲我有點困惑。

情況1:func a被定義爲沒有類型def或非嚴格類型def,然後被使用一次,編譯器在編譯時沒有問題推斷類型。

情況2:相同FUNC a多次使用的程序,編譯器不能100%確定是什麼類型,除非它重新計算對於給定參數的函數。

爲了避免計算損失,ghc向程序員抱怨說,它需要一個嚴格的類型def在a 才能正常工作。

我想在我的情況,assertEqual

assertEqual :: (Eq a, Show a) => String -> a -> a -> Assertion 

我得到了一個錯誤的類型定義時test3定義,我解釋說,它有2種可能的類型爲testcase3回報(展會和公式),並不知道如何繼續。

這聽起來是對的還是我完全沒有?

problem7.hs:

-- # 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 

月1日的情況: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. 

回答

4

它沒有那麼多的單態的限制,它是曖昧的決議按defaulting t鍵入變量帽子導致編譯失敗。

-- 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中就沒有使用它的類型。

如果你給了類型簽名綁定,而不是右手邊,

testcase3' :: Eq a => [a] 
testcase3' = flatten (List []) 

testcase3'' :: Num a => [a] 
testcase3'' = flatten (List []) 

兩個值會自己編譯成多態值,但仍然只testcase3''將在test3是可用的,因爲只有這樣才能引入所需的數字約束來允許默認。