我遇到了Haskell的问题。我的文本文件看起来像这样:
5.
7.
[(1,2,3),(4,5,6),(7,8,9),(10,11,12)].
我不知道如何获得前两个数字(上面的2和7)以及最后一行的列表。每行末尾都有点。
我尝试构建一个解析器,但是名为'readFile'的函数返回名为IO String的Monad。我不知道如何从这种类型的字符串中获取信息。
我更喜欢在一系列字符上工作。也许有一个函数可以从'IO String'转换为[Char]?
答案 0 :(得分:67)
我认为你对Haskell中的IO有一个基本的误解。特别是,你这样说:
也许有一个函数可以从'IO String'转换为[Char]?
不,没有 1 ,而且没有这样的功能是Haskell最重要的事情之一。
Haskell是一种非常有原则的语言。它试图保持“纯”函数(没有任何副作用,并在给出相同的输入时始终返回相同的结果)和“不纯”函数(具有类似读取文件,打印等副作用)之间的区别到屏幕,写入磁盘等)。规则是:
代码标记为纯或不纯的方式是使用类型系统。当你看到像
这样的函数签名时digitToInt :: String -> Int
你知道这个功能很纯粹。如果你给它String
它会返回Int
而且如果你给它Int
,它将总是返回相同的String
。另一方面,功能签名如
getLine :: IO String
impure ,因为String
的返回类型标有IO
。显然getLine
(读取一行用户输入)并不总是返回相同的String
,因为它取决于用户键入的内容。您不能在纯代码中使用此函数,因为添加即使最小的杂质也会污染纯代码。一旦你去IO
,你永远不会回去。
您可以将IO
视为包装器。当您看到特定类型(例如x :: IO String
)时,您应该将其解释为“x
是一种操作,在执行时会执行某些任意I / O,然后返回{{1}类型的内容1}}“(请注意,在Haskell中,String
和String
完全相同。)
那么您如何访问[Char]
行动中的值?幸运的是,函数IO
的类型是main
(它是执行某些I / O并返回IO ()
的操作,与返回任何内容相同)。因此,您始终可以使用()
内的IO
功能。当您执行Haskell程序时,您正在执行的是运行main
函数,这会导致程序定义中的所有I / O实际执行 - 例如,您可以从文件读取和写入,请求用户输入,写入stdout等等。
您可以考虑构建一个像这样的Haskell程序:
main
标记(基本上,您将其放在IO
块中)do
块中 - 这些是“纯粹”功能。do
函数将您在程序中定义的I / O操作排列在一起,使程序按照您的意愿执行(在任何您喜欢的位置穿插纯函数)。main
时,会导致执行所有这些I / O操作。所以,考虑到这一切,你如何编写你的程序?好吧,功能
main
将文件读取为readFile :: FilePath -> IO String
。所以我们可以使用它来获取文件的内容。功能
String
在换行符上拆分lines:: String -> [String]
,所以现在有一个String
列表,每个列表对应一行文件。功能
String
从列表中删除最后一个元素(这将消除每行上的最后一个init :: [a] -> [a]
)。功能
.
获取read :: (Read a) => String -> a
并将其转换为任意Haskell数据类型,例如String
或Int
。合理地结合这些功能将为您提供程序。
请注意,实际需要执行任何I / O的唯一时间是您正在读取文件。因此,这是程序中唯一需要使用Bool
标记的部分。程序的其余部分可以“纯粹”编写。
听起来你需要的是文章The IO Monad For People Who Simply Don't Care,它可以解释你的很多问题。不要被“monad”这个术语吓到 - 你不需要理解monad是什么来编写Haskell程序(注意这个段落是我答案中唯一一个使用“monad”这个词,尽管我承认我现在已经使用了四次......)
这是(我认为)你想写的程序
IO
要回答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)
关于将npfedwards
应用于lines
输出的评论,您需要意识到readFile text.txt
会为您提供readFile text.txt
,而且只有IO String
当您将其绑定到变量(使用contents <-
)时,您可以访问基础String
,以便可以将lines
应用于该变量。
请记住:一旦你去IO
,你永远不会回去。
1 我故意忽略unsafePerformIO
,因为正如名称所暗示的那样,它是非常不安全的!除非你真的知道你在做什么,否则不要使用它。
答案 1 :(得分:9)
作为编程菜鸟,我也被IO
混淆了。请记住,如果你去IO
,你永远不会出来。克里斯写了great explanation on why。我只是想提一些如何在monad中使用IO String
的例子。我将使用getLine读取用户输入并返回IO String
。
line <- getLine
所有这一切都将用户输入从getLine
绑定到名为line
的值。如果你在ghci中输入这个,并输入:type line
它将返回:
:type line
line :: String
但是等等! getLine
会返回IO String
:type getLine
getLine :: IO String
那么来自IO
的{{1}}发生了什么?发生了getLine
。 <-
是您的<-
朋友。它允许您显示monad中IO
污染的值,并将其与正常函数一起使用。 Monad很容易识别,因为它们以IO
开头。像这样:
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
:
<-
现在,如果在ghci中键入并检查 yourdata <- readFile "samplefile.txt"
的类型,您会发现它是一个简单的yourdata
。
String
答案 2 :(得分:7)
正如人们已经说过的,如果你有两个函数,一个是readStringFromFile :: FilePath -> IO String
,另一个是doTheRightThingWithString :: String -> Something
,那么你真的不需要从IO
转义一个字符串,因为您可以通过各种方式组合这两个功能:
fmap
IO
(IO
为Functor
):
fmap doTheRightThingWithString readStringFromFile
(<$>)
IO
IO
为Applicative
和(<$>) == fmap
):
import Control.Applicative
...
doTheRightThingWithString <$> readStringFromFile
使用liftM
IO
(liftM == fmap
):
import Control.Monad
...
liftM doTheRightThingWithString readStringFromFile
(>>=)
IO
IO
为Monad
,fmap == (<$>) == liftM == \f m -> 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
。
为什么你会这样做呢?好吧,有了这个,你将拥有纯和
您所用语言的 referentially transparent 程序(函数)。这意味着每个类型都是IO-free的函数是 pure 和 refereically transparent ,因此对于相同的参数,它将返回相同的值。例如,doTheRightThingWithString
会为同一Something
返回相同的String
。但是,不是无IO的readStringFromFile
每次都可以返回不同的字符串(因为文件可以更改),因此您无法从IO
中删除此类不正确的值。
答案 3 :(得分:4)
如果你有这种类型的解析器:
myParser :: String -> Foo
并使用
读取文件readFile "thisfile.txt"
然后您可以使用
读取和解析文件fmap myParser (readFile "thisfile.txt")
结果将是IO Foo
类型。
fmap
表示myParser
在IO内部运行。
另一种思考方式是myParser :: String -> Foo
,fmap myParser :: IO String -> IO Foo
。