我有一个文件number.txt
,其中包含大量数字,我将其读入IO String
,如下所示:
readNumber = readFile "number.txt" >>= return
在另一个函数中,我想创建一个Int
的列表,每个数字一个Int ......
让我们假设number.txt
的内容是:
1234567890
然后我希望我的函数返回[1,2,3,4,5,6,7,8,9,0]
。
我尝试了几个版本map
,mapM(_)
,liftM
和,和,但是,我每次都收到几条错误消息,我可以将其减少为
Couldn't match expected type `[m0 Char]'
with actual type `IO String'
我在磁盘上的最新版本如下:
module Main where
import Control.Monad
import Data.Char (digitToInt)
main = intify >>= putStrLn . show
readNumber = readFile "number.txt" >>= return
intify = mapM (liftM digitToInt) readNumber
所以,就我理解错误而言,我需要一些需要IO [a]
并返回[IO a]
的函数,但是我无法用hoogle找到这样的东西......只是反过来看存在
答案 0 :(得分:7)
除了这里的其他好答案之外,最好还谈谈如何阅读 [IO Char]
与IO [Char]
。特别是,您可以调用[IO Char]
“(延迟的)IO
行动的(直接)列表,其中会生成Char
s”和IO [Char]
“a(延迟){{ 1}}动作产生IO
s的列表。
重要的部分是上面“延迟”的位置---类型Char
和类型IO a
之间的主要区别在于前者最好被认为是一组指令在运行时执行,最终生成a
...而后者只是a
。
这一阶段的区别对于理解a
值的工作原理至关重要。值得注意的是,它在程序中非常流畅 - 像IO
或fmap
这样的函数允许我们查看相位区分。例如,请考虑以下函数
(>>=)
在这里,我们通过使用一个函数修改延迟操作(foo :: IO Int -- <-- our final result is an `IO` action
foo = fmap f getChar where -- <-- up here getChar is an `IO Char`, not a real one
f :: Char -> Int
f = Data.Char.ord -- <-- inside here we have a "real" `Char`
)来构建延迟操作(foo
),该函数查看在我们的延迟getChar
操作后才出现的世界已经跑了。
所以让我们打结这个结并回到手头的问题。为什么你不能将IO
变成IO [Char]
(以任何有意义的方式)?好吧,如果你正在查看一段可以访问[IO Char]
的代码,那么你要做的第一件事就是潜入IO [Char]
行动
IO
在floob = do chars <- (getChars :: IO [Char])
...
部分中我们可以访问...
,因为我们已“进入”chars :: [Char]
行动IO
。这意味着到目前为止,我们必须已经运行了生成该字符列表所需的任何运行时操作。我们已经让猫离开了monad而且我们无法以任何有意义的方式重新获得它,因为我们无法返回并“解读”每个角色。
(注意:我一直在说“以任何有意义的方式”,因为我们绝对可以使用getChars
将猫放回monad,但这不会让我们回到过去并且永远不会让它们在第一名。那艘船已航行。)
那么我们如何获得return
类型?好吧,我们必须知道(不运行任何[IO Char]
)我们想做什么样的IO
操作。例如,我们可以编写以下内容
IO
并立即执行类似
的操作replicate 10 getChar :: [IO Char]
没有运行take 5 (replicate 10 getChar)
动作 - 我们的列表结构立即可用,并且在运行时有机会到达之前不会延迟。但请注意,我们必须准确了解我们要执行的IO
操作的结构,以便创建类型IO
。也就是说,我们可以使用另一个[IO Char]
水平来窥视现实世界,以确定我们行动的参数
IO
此片段的类型为do len <- (figureOutLengthOfReadWithoutActuallyReading :: IO Int)
return $ replicate len getChar
。要运行它,我们必须逐步执行IO [IO Char]
两次,我们必须让运行时执行两个 IO
操作,首先确定长度和然后第二个实际操作我们的IO
行动列表。
IO Char
上述函数sequence :: [IO a] -> IO [a]
是执行包含sequence
个动作序列的一些结构的常用方法。我们可以用它来做我们的两阶段读取
IO
答案 1 :(得分:6)
你混淆了一些事情:
readNumber = readFile "number.txt" >>= return
回报是不必要的,只是把它留下来。
这是一个工作版本:
module Main where
import Data.Char (digitToInt)
main :: IO ()
main = intify >>= print
readNumber :: IO String
readNumber = readFile "number.txt"
intify :: IO [Int]
intify = fmap (map digitToInt) readNumber
答案 2 :(得分:2)
这样的功能不可能存在,因为您可以在不调用任何IO
的情况下评估列表的长度。
可能的是:
imbue' :: IO [a] -> IO [IO a]
imbue' = fmap $ map return
当然要概括为
imbue :: (Functor f, Monad m) => m (f a) -> m (f (m a))
imbue = liftM $ fmap return
然后,您可以这样做,
quun :: IO [Char]
bar :: [IO Char] -> IO Y
main = do
actsList <- imbue quun
y <- bar actsLists
...
只是,关于使用[IO Char]
的全部内容毫无意义:它完全等同于仅使用“纯值”列表的更直接的方式,只使用IO
monad“outside” ;马库斯的回答显示了如何做到这一点。
答案 3 :(得分:0)
您真的需要许多不同的辅助功能吗?因为你可能只写
main = do
file <- readFile "number.txt"
let digits = map digitToInt file
print digits
或者,如果您确实需要将它们分开,请尽量减少IO
签名的数量:
readNumber = readFile "number.txt" --Will be IO String
intify = map digitToInt --Will be String -> [Int], not IO
main = readNumber >>= print . intify