我如何解析Haskell中的IO字符串?
我遇到了Haskell的问题。 我有这样的文本文件:
5.
7.
[(1,2,3),(4,5,6),(7,8,9),(10,11,12)].
我不知道怎样才能得到前两个数字(上面的2和7)和最后一行的列表。 每行的末尾都有点。
我试图构建一个解析器,但名为'readFile'的函数返回称为IO String的Monad。 我不知道如何从这种类型的字符串中获取信息。
我更喜欢在一系列字符上工作。 也许有一个函数可以从'IO String'转换为[Char]?
我认为你对Haskell中的IO有一个基本的误解。 特别是,你这样说:
也许有一个函数可以从'IO String'转换为[Char]?
不,不存在,事实上没有这样的功能是Haskell最重要的事情之一。
Haskell是一种非常有原则的语言。 它试图保持“纯”功能(它没有任何副作用,并且在给予相同输入时总是返回相同的结果)和“不纯”功能(其具有从文件读取,打印到屏幕,写入磁盘等)。 规则是:
代码被标记为纯粹或不纯的方式是使用类型系统。 当你看到一个函数签名像
digitToInt :: String -> Int
你知道这个函数是纯粹的。 如果你给它一个String
它会返回一个Int
,而且它总是会返回相同的Int
,如果你给它相同的String
。 另一方面,一个函数签名像
getLine :: IO String
是不纯的,因为String
的返回类型是用IO
标记的。 显然getLine
(读取一行用户输入)不会总是返回相同的String
,因为它取决于用户输入的内容。不能在纯代码中使用此函数,因为即使添加最小的杂质也会污染纯代码。 一旦你去了IO
你永远不会回去。
你可以把IO
想象成一个包装。 当你看到一个特定的类型时,例如x :: IO String
,你应该把它解释为“ x
是一个在执行时执行一些任意I / O然后返回String
类型的动作”(注意在Haskell, String
和[Char]
完全一样)。
那么你如何获得IO
操作的值呢? 幸运的是,函数main
的类型是IO ()
(这是一个执行一些I / O和返回()
,与返回任何内容相同)。 所以你可以在main
里面使用你的IO
函数。 当你执行一个Haskell程序时,你正在做的是运行main
函数,这会导致程序定义中的所有I / O被实际执行 - 例如,你可以读写文件,询问用户输入,写入标准输出等。
你可以考虑像这样构建一个Haskell程序:
IO
标记(基本上,你把它放在一个do
块中) do
块中 - 这些是“纯”功能。 main
函数将你定义的I / O操作按顺序排列在一起,使得程序能够按照你想要的操作(穿插纯函数,无论你喜欢什么)。 main
,会导致所有这些I / O操作被执行。 所以,考虑到这一点,你如何编写你的程序? 那么,功能
readFile :: FilePath -> IO String
以String
读取文件。 所以我们可以使用它来获取文件的内容。 功能
lines:: String -> [String]
在换行符上分割一个String
,所以现在你有一个String
的列表,每个对应于文件的一行。 功能
init :: [a] -> [a]
滴从列表中(这将摆脱最终的最后一个元素.
每行)。 功能
read :: (Read a) => String -> a
接受一个String
并将其转换为任意的Haskell数据类型,例如Int
或Bool
。 合理组合这些功能将为您提供程序。
请注意,您实际需要执行任何I / O的唯一时间是在阅读文件时。 因此,这是需要使用IO
标签的程序的唯一部分。 程序的其余部分可以写成“纯粹”。
这听起来像你需要的是文章IO Monad对于那些根本不在乎的人,这应该解释你的很多问题。 不要被“monad”这个词所吓倒 - 你不需要明白monad编写Haskell程序是什么(注意,这段文字是我答案中唯一使用单词“monad”的,尽管我承认我现在已经使用了四次...)
这是我想要编写的程序
run :: IO (Int, Int, [(Int,Int,Int)])
run = do
contents <- readFile "text.txt" -- use '<-' here so that 'contents' is a String
let [a,b,c] = lines contents -- split on newlines
let firstLine = read (init a) -- 'init' drops the trailing period
let secondLine = read (init b)
let thirdLine = read (init c) -- this reads a list of Int-tuples
return (firstLine, secondLine, thirdLine)
要回答关于将lines
应用于readFile text.txt
的输出的npfedwards
注释,您需要认识到readFile text.txt
为您提供了一个IO String
,并且仅当您将它绑定到变量(使用contents <-
)时访问底层String
,以便您可以对其应用lines
。
记住:一旦你去了IO
,你永远不会回去。
1我故意忽略unsafePerformIO
因为正如名称所暗示的那样,它非常不安全! 除非你真的知道你在做什么,否则千万不要使用它。
作为一种编程小白,我也被迷茫IO
秒。 只要记住,如果你去IO
你永远不会出来。 克里斯为什么写了一个很好的解释。 我只是认为这可能有助于举例说明如何在monad中使用IO String
。 我将使用读取用户输入并返回IO String
getLine。
line <- getLine
所有这些都将getLine
的用户输入绑定到一个名为line
的值。 如果你在ghci中输入这个,输入:type line
它会返回:
:type line
line :: String
可是等等! getLine
返回一个IO String
:type getLine
getLine :: IO String
那么来自getLine
的IO
发生了什么? <-
发生了什么事。 <-
是你的IO
朋友。 它可以让你在monad中显示被IO
污染的值,并将其用于正常的功能。 Monad很容易识别,因为它们以do
开始。 像这样:
main = do
putStrLn "How much do you love Haskell?"
amount <- getLine
putStrln ("You love Haskell this much: " ++ amount)
如果你和我一样,你很快就会发现liftIO
是你的下一个最好的单子朋友,并且$
有助于减少你需要编写的括号。
那么如何从readFile
获取信息? 那么如果readFile
的输出是IO String
就像这样:
:type readFile
readFile :: FilePath -> IO String
那么你需要的只是你友善的<-
:
yourdata <- readFile "samplefile.txt"
现在,如果类型,在ghci中,检查的类型yourdata
你会发现这是一个简单的String
。
:type yourdata
text :: String
正如人们已经说过的,如果你有两个函数,一个是readStringFromFile :: FilePath -> IO String
,另一个是doTheRightThingWithString :: String -> Something
,那么你真的不需要从IO
转义出一个字符串,因为你可以以各种方式组合这两个功能:
使用IO
fmap
( IO
是Functor
):
fmap doTheRightThingWithString readStringFromFile
使用IO
(<$>)
( IO
是Applicative
和(<$>) == fmap
):
import Control.Applicative
...
doTheRightThingWithString <$> readStringFromFile
用liftM
for IO
( liftM == fmap
):
import Control.Monad
...
liftM doTheRightThingWithString readStringFromFile
(>>=)
表示IO
( IO
是Monad
, fmap == (<$>) == liftM == fm -> m >>= return . f
):
readStringFromFile >>= string -> return (doTheRightThingWithString string)
readStringFromFile >>= string -> return $ doTheRightThingWithString string
readStringFromFile >>= return . doTheRightThingWithString
return . doTheRightThingWithString =<< readStringFromFile
随着do
记号:
do
...
string <- readStringFromFile
-- ^ you escape String from IO but only inside this do-block
let result = doTheRightThingWithString string
...
return result
每次你会得到IO Something
。
为什么你会想这样做? 那么,有了这个,你就可以在你的语言中使用纯粹且引用透明的程序(函数)。 这意味着每个无IO类型的函数都是纯粹的,并且是引用透明的,因此对于相同的参数,它将返回相同的值。 例如, doTheRightThingWithString
会为相同的String
返回相同的Something
。 然而,不是无IO的readStringFromFile
可以每次返回不同的字符串(因为文件可以改变),所以你不能从IO
转义这种不确定的值。