2012-07-29 6 views
8

Zacząłem robić 99 problemów z haskellami i byłem na problem 7, a moje unittests wysadzały w powietrze.Wyjaśnij mi ograniczenie monomorfizmu?

Widocznie, to ze względu na to: http://www.haskell.org/haskellwiki/Monomorphism_restriction

Chciałem tylko upewnić się, że zrozumiał to prawidłowo, ponieważ jestem trochę zdezorientowany.

Sytuacja 1: func a jest zdefiniowana bez defektu typu lub z nieszablonowym defektem typu, a następnie używana raz, kompilator nie ma problemów z określeniem typu w czasie kompilacji.

sytuacja 2: ten sam func a jest używany wiele razy w programie, kompilator nie może być w 100% pewny, jaki jest typ, chyba że przelicza on funkcję dla podanych argumentów.

Aby uniknąć utraty obliczeń, ghc skarży się programistom, że potrzebuje ścisłego typu def na a , aby działał poprawnie.

myślę, że w mojej sytuacji, assertEqual ma def typu z

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

ja otrzymuję błąd podczas test3 określono, że interpretowane jako mówiąc, że miał 2 możliwe typy dla powrotu testcase3 (pokaż i Eq) i nie wiedzieli, jak kontynuować.

Czy to brzmi poprawnie, czy jestem całkowicie wyłączony?

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-ty sytuacja: 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> 

2-ty sytuacja: 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. 

Odpowiedz

4

To nie tyle ograniczenie monomorfizm, to rozdzielczość niejednoznaczne wpisz zmienne: defaulting t kapelusz powoduje awarię kompilacji.

-- 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 nie nakłada żadnych ograniczeń na typ zmiennej a, więc nie ma problemu z definicją testcase3 jako takiego, byłoby polimorficzny.

Ale kiedy go używać w test3,

test3 = TestCase $ assertEmptyList "" testcase3 -- '' 

dziedziczyć więzy

assertEmptyList :: (Eq a, Show a) => String -> [a] -> Assertion 

Teraz kompilator musi dowiedzieć się, w jaki typ testcase3 powinien być tam stosowany. Nie ma wystarczającego kontekstu, aby określić typ, więc kompilator próbuje domyślnie rozwiązać zmienną typu. Zgodnie z defaulting rules, kontekst (Eq a, Show a) nie może zostać rozwiązany domyślnie, ponieważ tylko konteksty obejmujące co najmniej jedną klasę numeryczną kwalifikują się do defaultowania. Tak więc kompilacja kończy się niepowodzeniem z powodu niejednoznacznej zmiennej typu.

i testcase3'' są jednak objęte ograniczeniem monomorfizmu ze względu na sygnaturę typu wyrażenia, która nakłada ograniczenia po prawej stronie definicji, która jest dziedziczona po lewej stronie.

testcase3' nie kompiluje się z tego powodu, niezależnie od tego, czy jest używany w asercji.

otrzymuje domyślną wartość [Integer], ponieważ sygnatura typu wyrażenia nakłada ograniczenie numeryczne. Tak więc, gdy typ jest monomorfizowany dla testcase'', zmienna typu ograniczonego jest domyślnie ustawiona na Integer. Wtedy nie ma mowy o typie, w którym jest używany w test3.

Jeśli dał podpisy typu do wiązania zamiast do prawej strony,

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

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

obu wartości byłyby opracowywane we własnym wartościom polimorficznych, ale nadal tylko testcase3'' byłby użyteczny w test3, ponieważ tylko wprowadza wymagane ograniczenie numeryczne, aby umożliwić niewywiązywanie się z płatności.