因为我一直在学习haskell,所以我很享受纯粹的部分,但现在我在monadic和IO部分遇到困难,可能会遇到一些人真正对这种语言感到愤怒的事情.我解决了一个项目euler问题,我简单地想要一个可变数组,因为我必须经常通过索引更新元素.我试过矢量但无法让它们工作,所以我尝试了Data.Array.IO.我可以读取和写入元素,但我不能按照我想要的方式在终端中显示数组.到目前为止,我有这个.
test = do
arr <- newArray (1,10) 37 :: IO (IOArray Int Int)
a <- readArray arr 1
writeArray arr 1 64
b <- readArray arr 1
dispArray arr
return ()
dispArray arr = do
(a,b) <- getBounds arr
printf "["
dispArray' arr a
printf "]\n"
where dispArray' arr i = do
(a,b) <- getBounds arr
if i < a || i > b
then return ()
else do
v <- readArray arr i
print v
dispArray' arr (i+1)
Run Code Online (Sandbox Code Playgroud)
正如您所料,这个输出是这样的:
[64
37
37
37
37
37
37
37
37
37
]
Run Code Online (Sandbox Code Playgroud)
但这很不方便,我希望[64,37,37,37....这样.我见过类似的函数toList,但我不想这样.我不希望每次显示时都转换为列表.所以我想我需要使用printf.所以我换成print v了printf " %s," (show v).但这不编译.我不知道为什么.我认为它会因为print :: Show a => a -> IO ()和show :: Show a => a -> String那么,为什么不是工作,因为%s标志着一个字符串?所以我接着打电话给对方.看看printf是否会起作用.
printf " %s," "hello"
print v
Run Code Online (Sandbox Code Playgroud)
编译和显示:
[ hello,64
hello,37
hello,37
hello,37
hello,37
hello,37
hello,37
hello,37
hello,37
hello,37
]
Run Code Online (Sandbox Code Playgroud)
为什么我不能使用show v?为什么haskell IO对初学者如此愤怒?
你想要的咒语是:
putStr (show v)
Run Code Online (Sandbox Code Playgroud)
打印出来v没有换行符.
这是一个有趣的类型检查难题.
printf生成调用的错误消息是
Could not deduce (PrintfType (m a0))
arising from the ambiguity check for `dispArray'
Run Code Online (Sandbox Code Playgroud)
短语Could not deduce和ambiguity通常暗示了一个事实,即GHC没有足够的类型信息,以完成这个程序应该如何打字.这可能是一个真正的类型错误,但也可以通过提供更多的类型信息来修复它(这就是这里的情况).
这里的罪魁祸首是printf,与可变阵列接口的灵活性相结合,而不是Haskell的IO系统.类型printf是一个巧妙的黑客,但仍然是一个黑客.为了知道仅依赖于格式字符串的各种类型的灵活数量的参数,printf具有不安全且信息量非常大的类型:
printf :: PrintfType r => String -> r
Run Code Online (Sandbox Code Playgroud)
所以我们真正知道的是第一个参数是类型的String.其余的可以是r类型类中的任何类型PrintfType.
实例的细节无关紧要.有趣的是show产生一个String,如果我们应用于printf格式字符串然后show生成第二个字符串,我们仍然留下一个相当无信息的类型:
> :t printf "%s," (show 2)
printf "%s," (show 2) :: PrintfType t => t
Run Code Online (Sandbox Code Playgroud)
特别是,这里没有迹象表明结果是在IOmonad中.
如果GHC可以从您所处的背景中得出结论,这通常不会成为问题IO.但内dispArray',您呼叫的唯一的其他功能readArray,getBounds,return(和dispArray'递归).这些函数都没有指定它存在于IO任何一个中.特别是,所有数组函数都在monad上重载,例如:
getBounds :: (Ix i, MArray a e m) => a i e -> m (i, i)
Run Code Online (Sandbox Code Playgroud)
(事实上,getBounds例如,也可以在STmonad环境中工作.)因此,没有任何东西dispArray'可以决定你的生活IO.而这又意味着GHC无法解决这种类型printf.
正如我所说,这是所需灵活性的结果printf,printf它本身无法提供这些信息,而且必须在外部提供.
解决方案很简单.正如其中一条评论中所建议的那样,将调用的结果类型注释为printf:
printf "%s," (show v) :: IO ()
Run Code Online (Sandbox Code Playgroud)
printf无论如何你正在使用(如果你实际上只对十进制数组的数组感兴趣),你也可以使用:
printf "%d," v :: IO ()
Run Code Online (Sandbox Code Playgroud)
对于读者来说,为定义中的任何其他内容提供类型签名也是足够的(但不太清楚),dispArray'以便修复返回类型IO ().例如,您可以在表达式return ()的then-branch中注释if:
return () :: IO ()
Run Code Online (Sandbox Code Playgroud)