2013-04-03 92 views
4

正如我一直在学习haskell,我已经享受纯粹的部分,但现在我蹒跚通过monadic和IO部分,并可能经历了一些人真正感到愤怒的语言。我解决了一个项目euler问题,我简单地想要一个可变数组,因为我必须经常通过索引来更新元素。我试过Vectors,但无法让它们工作,所以我尝试了Data.Array.IO。我可以读取和写入元素,但无法以我想要的方式在终端中显示数组。到目前为止,我有这个。显示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) 

这正如你所期望的输出中是这样的:

[64 
37 
37 
37 
37 
37 
37 
37 
37 
37 
] 

但是,这是不方便的,我想这个[64,37,37,37....这样。我见过类似toList的功能,但我不想要这样的功能。我不想在每次显示时转换为列表。所以我想我需要使用printf。所以我用printf " %s," (show v)替换了print v。但是这不能编译。我不知道为什么。我认为这将是因为print :: Show a => a -> IO()show :: Show a => a -> String所以为什么它不会工作,因为%s表示一个字符串?所以我接着打电话给对方。看看printf是否可以工作。

printf " %s," "hello" 
print v 

来编译和显示:

[ hello,64 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
] 

我为什么不能用show v?为什么Haskell IO对初学者如此愤怒?

+0

只是出于好奇,究竟是想用'的printf“%S”(编译时收到错误消息show v)'? – jwodder

+0

'无法推断(PrintfType(m a0)) 由'dispArray'的歧义检查产生 我试过通过'::'添加类型,但是我觉得monads的类型是令人难以置信的神秘。 – DiegoNolan

+0

Ps这是'ST'阵列的一个很好的用例。他们让你有一个可变的东西隐藏在一个纯粹的功能接口 – jozefg

回答

6

这是一个有趣的类型检查谜题。

该呼叫到printf产生的错误信息是

Could not deduce (PrintfType (m a0)) 
    arising from the ambiguity check for `dispArray' 

短语Could not deduceambiguity通常在一个事实,即具有GHC以结束这个程序应该如何被键入 不足型信息提示。这可能是一个真正的类型错误,但也可以通过提供更多类型信息来解决它(这里就是这种情况)。

这里的罪魁祸首真的是printf,加上可变数组接口的灵活性,而不是Haskell的IO系统。 printf的类型是一个巧妙的黑客攻击,但仍然是黑客攻击。为了知道那仅仅依靠格式字符串各类参数的灵活数量,printf有一个类型,是不是很安全,也非常丰富:

printf :: PrintfType r => String -> r 

所以我们真正知道肯定的是,第一个参数是类型String。其余的可以是类型为PrintfType的任何类型r

实例的详细信息无关紧要。有趣的是,show产生String,如果我们运用printf的格式字符串,然后一个show -produced第二个字符串,我们仍然留下了一个相当不提供信息的类型:

> :t printf "%s," (show 2) 
printf "%s," (show 2) :: PrintfType t => t 

特别是,没有迹象显示这里的结果是在IO单子中。

这通常不会是一个问题,如果GHC可以从上下文中得出结论,你在IO。但是在dispArray'中,您正在调用的唯一其他功能是readArraygetBoundsreturn(和dispArray'递归)。这些功能都没有说明它也住在IO之内。特别是,所有的阵列功能超载过单子,例如:

getBounds :: (Ix i, MArray a e m) => a i e -> m (i, i) 

(事实上,getBounds可能,例如,同样工作在ST单子上下文。),这样就根本没有在dispArray'那确定你住在IO。而这又意味着GHC无法解决printf的类型。

正如我所说,这是printf所需灵活性的结果,printf本身无法提供此信息,并且必须在外部可用。

该解决方案非常简单。作为一个评论认为,这足以将调用的结果类型注释到printf

printf "%s," (show v) :: IO() 

当你使用printf无论如何(如果你实际上只在十进制数字阵列感兴趣),你也可以使用:

printf "%d," v :: IO() 

这也将足够了(但不太清楚的读者),给出的dispArray'定义内的任何其他类型的签名,使其固定收益类型为IO()。例如,你可以注释return()then分枝的if表达:

return() :: IO() 
6

你想要的咒语是:

putStr (show v) 

打印出v没有一个换行符。

+0

'putStr $ show v'但为什么不能使用printf,如果我想在较少的行中使用不错的格式? – DiegoNolan

+3

很难说,除非你做你的榜样容易,我编的,但我的猜测是,你正在由单态限制咬伤,因为你没有提供明确的类型签名。你的错误与Haskell'IO'无关。 GHC根本无法推断出'printf'应该是什么类型,因为它滥用了类型魔法。 –