4

当我一直在学习 haskell 时,我很喜欢纯粹的部分,但现在我在 monadic 和 IO 部分中磕磕绊绊,可能正在经历一些人觉得这门语言真正令人愤怒的地方。我解决了一个项目欧拉问题,我想要一个可变数组,因为我必须经常按索引更新元素。我尝试了 Vectors,但无法让它们工作,所以我尝试了 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)

正如您所期望的那样,输出是这样的:

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

但这很不方便,我想要[64,37,37,37....这样。我见过类似的函数toList,但我不想要这个。我不想每次显示时都转换为列表。所以我想我需要使用printf. 所以我换成print vprintf " %s," (show 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 对初学者如此恼火?

4

2 回答 2

6

你想要的咒语是:

putStr (show v)

打印出来v没有换行符。

于 2013-04-03T02:10:34.863 回答
6

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

调用printf产生的错误消息是

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

这些短语通常暗示 GHC 的类型信息不足,无法得出该程序应如何键入的结论Could not deduceambiguity这可能是一个真正的类型错误,但也有可能通过提供更多类型信息来修复它(这里就是这种情况)。

这里的罪魁祸首真的是printf,结合可变数组接口的灵活性,而不是 Haskell 的 IO 系统。的类型printf是一种巧妙的 hack,但仍然是一种 hack。为了了解仅依赖于格式字符串的各种类型的灵活数量的参数,printf具有一种不是很安全也不是非常有用的类型:

printf :: PrintfType r => String -> r

所以我们真正确定的是第一个参数是 type String。其余的可以是r类型类中的任何类型PrintfType

实例的细节无关紧要。有趣的是,它show会产生一个String,如果我们应用printf到一个格式字符串,然后是一个show- 产生的第二个字符串,我们仍然会得到一个相当无信息的类型:

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

特别是,这里没有迹象表明结果是在IOmonad 中。

如果 GHC 可以从您所在的上下文中得出结论,这通常不会成为问题IO。但在 内dispArray',您调用的唯一其他函数是readArray, getBounds, return(和dispArray'递归)。这些函数都没有指定它存在于IO其中。特别是,所有数组函数都在 monad 上重载,例如:

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

(实际上,getBounds例如,也可以在STmonad 上下文中工作。)所以根本没有任何东西dispArray'可以决定你住在IO. 这反过来意味着 GHC 无法解析printf.

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

解决方案很简单。正如其中一条评论所建议的,将调用的结果类型注释为printf

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

无论如何您都在使用printf(并且如果您实际上只对十进制数数组感兴趣),您还可以使用:

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

为定义中的任何其他内容提供类型签名也足够(但读者不太清楚),dispArray'以便将返回类型固定为IO (). 例如,您可以在表达式return ()then-branch 中注释if

return () :: IO ()
于 2013-04-03T08:39:36.507 回答