2014-01-19 130 views
5

我编写了一个Haskell程序,并得到了编译错误,我不明白。了解一个Haskell类型歧义的案例

程序应该:

  • 获取命令行参数
  • 拼接标记化参数回单String
  • 阅读StringNestedList数据类型
  • 拼合NestedListList
  • 打印List

不幸的是,由于类型不明确,它不会编译。

Haskell代码:

{- 
    Run like this: 
    $ ./prog List [Elem 1, List [Elem 2, List [Elem 3, Elem 4], Elem 5]] 
    Output: [1,2,3,4,5] 
-} 
import System.Environment 
import Data.List 

data NestedList a = Elem a | List [NestedList a] 
    deriving (Read) 

main = do 
    args <- getArgs 
    print . flatten . read $ intercalate " " args 

flatten :: NestedList a -> [a] 
flatten (Elem x) = [x] 
flatten (List x) = concatMap flatten x 

编译错误:

prog.hs:8:21: 
    Ambiguous type variable `a0' in the constraints: 
     (Read a0) arising from a use of `read' at prog.hs:8:21-24 
     (Show a0) arising from a use of `print' at prog.hs:8:3-7 
    Probable fix: add a type signature that fixes these type variable(s) 
    In the second argument of `(.)', namely `read' 
    In the second argument of `(.)', namely `flatten . read' 
    In the expression: print . flatten . read 

有人能帮助我了解如何/为什么有型歧义和我怎样才能使代码明确。

+0

您认为GHC应该找到什么类型? – misterbee

回答

7

只要类型变量由于函数应用而消失,Haskell中就会出现模糊类型。你在这里的那个,read/show是常见的。这里的问题:

让我们尝试读取一个字符串,这个操作有型

read :: Read a => String -> a 

这样,如果我们给它一个字符串,我们只得到一个类型,看起来像

read "()" :: Read a => a 

换句话说,类型系统还没有能够选择具体的类型---它只是知道,无论答案是什么,它必须是Read able。

的问题是,如果我们回头周围并出示马上我们将函数

show :: Show a => a -> String 

这也不能完全指定类型a。结合他们给了我们

show (read "()") :: String 

,我们已经失去了所有的机会在什么中间型应该已经决定。

由于这种歧义,Haskell不允许使用这种表达式。你通过插入一个完全限制类型的函数来修复它。一种常见的方法是使用功能asTypeOf

asTypeOf :: a -> a -> a 
asTypeOf = const 

,其确保所述第一和第二参数具有相同的类型。

> show (read "()" `asTypeOf`()) :: String 
"()" 

在您的特定例子中,你需要确定什么aNestedList a。一个简单的方法是明确地将flatten作为具体类型。

print . (flatten :: NestedList Int -> [Int]) . read $ concat args 
+1

感谢您的精彩解释!我删除了类型歧义,并按预期运行! –

+0

很高兴能帮到你! –

4

这是一个经典的问题。类型类的“ad hoc”多态性使得类型推断不完整,并且你刚刚被咬伤。让我们看看这些作品。对于

Read a => Read (NestedList a) 
Show a => Show (NestedList a) 
Read a => Read [a] 
Show a => Show [a] 

read :: Read x => String -> x 
flatten :: NestedList a -> [a] 
print :: Show y => y -> IO() 

,我们还会有机器产生的情况下,现在让我们来解决我们得到的公式,当我们尝试构建组成。

print .  flatten    . read 

     y = [a]   NestedList a = x 

这意味着我们需要

Show [a]      Read (NestedList a) 

,从而

Show a      Read a 

,我们已经使用了我们的所有信息,而不确定a,因此相关ReadShow实例。

正如J. Abrahamson已经建议的那样,您需要做一些决定a的事情。有很多方法可以做到这一点。我倾向于倾向于使用类型注释来编写奇特的术语,其唯一目的是使类型更加明显。我第二个建议给组合中的一个组件提供一个类型,但是我可能选择(read :: String -> NestedList Int),因为这是引入含糊不清的类型的操作的操作。

+0

我以前很喜欢'asTypeOf',直到我开始发现自己在写'(\'asTypeOf \'(undefined :: Etc))''。就像这样,为了使它工作,我必须将我的示例从'read'3“'更改为'read”()“'。所有说,类型注释是老板。 –