我写了一个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
有人可以帮助我理解如何/为什么存在类型模糊以及如何使代码明确无误。
答案 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
"()"
在您的特定示例中,您需要确定a
中NestedList 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
,因此也确定了相关的Read
和Show
个实例。
正如J. Abrahamson已经建议的那样,你需要做一些决定a
的事情。有很多方法可以做到这一点。我倾向于选择类型注释来编写奇怪的术语,其唯一目的是使类型更明显。我的第二个建议是给一个组合中的一个组件提供一个类型,但我可能会选择(read :: String -> NestedList Int)
,因为这是引入模糊类型的操作。