理解Haskell Type-Ambiguity的案例

时间:2014-01-19 18:15:50

标签: haskell ghc ambiguity

我写了一个Haskell程序并得到了一个我不明白的编译错误。

该计划应该:

  • 获取命令行参数
  • 将标记化参数连接回单个String
  • String读入NestedList数据类型
  • NestedList展平为List
  • 打印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

有人可以帮助我理解如何/为什么存在类型模糊以及如何使代码明确无误。

2 个答案:

答案 0 :(得分:7)

每当类型变量因函数应用而消失时,Haskell中就会出现不明确的类型。你在这里的read/show很常见。这是问题所在:

让我们尝试读取一个字符串,此操作的类型为

read :: Read a => String -> a

这样如果我们给它一个字符串,我们只会得到一个类似

的类型
read "()" :: Read a => a

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

问题在于,如果我们回头并立即显示,我们正在应用一个函数

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 :(得分:4)

这是一个经典问题。类型类的“ad hoc”多态性使得类型推断不完整,而您刚被咬过。让我们来看看这些作品。

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

我们还将为

提供机器生成的实例
Read a => Read (NestedList a)
Show a => Show (NestedList a)
Read a => Read [a]
Show a => Show [a]

现在让我们解决当我们尝试构建组合时得到的方程式。

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),因为这是引入模糊类型的操作。