8

私は99のhaskell問題をやり始めました、そして私は問題7にあり、私のユニットテストは爆発していました。

どうやら、それはこれによるものです:http: //www.haskell.org/haskellwiki/Monomorphism_restriction

ちょっと混乱しているので、これを正しく理解していることを確認したかっただけです。

状況1:funcaは、型defなしまたは非厳密型defで定義され、その後1回使用されると、コンパイラーはコンパイル時に型を推測するのに問題はありません。

状況2:同じ関数aがプログラムで何度も使用されている場合、コンパイラーは、指定された引数の関数を再計算しない限り、型が何であるかを100%確信することはできません。

a 計算の損失を回避するために、ghcは、正しく機能するために厳密な型defが必要であるとプログラマーに不平を言います。

私の状況でassertEqualは、タイプdefは

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

が定義されたときにエラーが発生しました。これは、(ShowとEq)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

最初の状況: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番目の状況: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

1 に答える 1

4

モノモーフィズムの制限ではなく、コンパイルの失敗を引き起こすデフォルト設定によるあいまいな型変数の解決です。

-- 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なければなりません。型を判別するのに十分なコンテキストがないため、コンパイラはデフォルトで型変数を解決しようとします。デフォルト化の規則によれば、(Eq a, Show a)少なくとも 1 つの数値クラスを含むコンテキストのみがデフォルト化の対象となるため、デフォルト化によってコンテキストを解決することはできません。したがって、あいまいな型変数が原因でコンパイルが失敗します。

testcase3'ただし、testcase3''左側に継承される定義の右側に制約を課す式型シグネチャによる単型性の制限に該当します。

testcase3'アサーションで使用されているかどうかに関係なく、そのためにコンパイルに失敗します。

testcase3''[Integer]式の型シグネチャが数値制約を課すため、デフォルトで取得されます。したがって、型が に対して単形化されてtestcase''いる場合、制約された型変数はデフォルトで に設定されIntegerます。それなら、それが で使用されるタイプの問題はありませんtest3

右側ではなくバインディングに型シグネチャを指定した場合、

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

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

両方の値は独自に多相値にコンパイルされますが、デフォルト設定を許可するために必要な数値制約を導入するだけなので、 でのみtestcase3''使用できます。test3

于 2012-07-29T19:42:44.400 に答える