Sim*_*mon 28 string io monads parsing haskell
我遇到了Haskell的问题.我的文本文件看起来像这样:
5.
7.
[(1,2,3),(4,5,6),(7,8,9),(10,11,12)].
Run Code Online (Sandbox Code Playgroud)
我不知道如何获得前两个数字(上面的2和7)和最后一行的列表.每行末尾有点.
我试图构建一个解析器,但是名为'readFile'的函数返回Monad,称为IO String.我不知道如何从这种类型的字符串中获取信息.
我更喜欢在一系列字符上工作.也许有一个函数可以从'IO String'转换为[Char]?
Chr*_*lor 73
我认为你对Haskell中的IO有一个基本的误解.特别是,你这样说:
也许有一个函数可以从'IO String'转换为[Char]?
不,没有1,并且没有这样的功能是Haskell最重要的事情之一.
Haskell是一种非常有原则的语言.它试图保持"纯"函数(没有任何副作用,并在给出相同的输入时始终返回相同的结果)和"不纯"函数(具有类似读取文件,打印等副作用)之间的区别到屏幕,写入磁盘等).规则是:
代码标记为纯或不纯的方式是使用类型系统.当你看到像这样的函数签名时
digitToInt :: String -> Int
Run Code Online (Sandbox Code Playgroud)
你知道这个功能是纯粹的.如果你给它一个String
它将返回一个Int
,而且如果你给它相同它将总是返回Int
相同String
.另一方面,功能签名就像
getLine :: IO String
Run Code Online (Sandbox Code Playgroud)
是不纯的,因为返回类型String
标有IO
.显然getLine
(读取一行用户输入)并不总是返回相同String
,因为它取决于用户键入的内容.您不能在纯代码中使用此函数,因为添加即使是最小的杂质也会污染纯净的码.一旦你离开,IO
你永远不会回去.
你可以把它想象IO
成一个包装器.例如,当你看到一个特定的类型时,x :: IO String
你应该将其解释为" x
是一个动作,当执行时,执行一些任意的I/O然后返回类型的东西String
"(请注意,在Haskell中,String
并且[Char]
完全相同事情).
那么如何从IO
动作中获取值?幸运的是,函数的类型main
是IO ()
(它是一个执行某些I/O并返回的操作()
,与返回任何内容相同).所以你总是可以在IO
里面使用你的功能main
.当您执行Haskell程序时,您正在执行的是运行该main
函数,这会导致程序定义中的所有I/O实际执行 - 例如,您可以从文件读取和写入,请求用户输入,写入到stdout等等
您可以考虑构造一个像这样的Haskell程序:
IO
代码都会获得标记(基本上,你把它放在一个do
块中)do
块中 - 这些是"纯"函数.main
函数将您按照顺序定义的I/O操作排列在一起,使程序按您希望的方式执行(在任何您喜欢的地方穿插纯函数).main
,将导致执行所有这些I/O操作.那么,考虑到这一切,你如何编写你的程序?好吧,功能
readFile :: FilePath -> IO String
Run Code Online (Sandbox Code Playgroud)
将文件读取为String
.所以我们可以使用它来获取文件的内容.功能
lines:: String -> [String]
Run Code Online (Sandbox Code Playgroud)
拆分String
换行符,所以现在你有一个String
s 列表,每个列表对应一个文件行.功能
init :: [a] -> [a]
Run Code Online (Sandbox Code Playgroud)
从列表中删除最后一个元素(这将消除.
每行的最后一个元素).功能
read :: (Read a) => String -> a
Run Code Online (Sandbox Code Playgroud)
将a String
转换为任意Haskell数据类型,例如Int
或Bool
.合理地结合这些功能将为您提供程序.
请注意,实际需要执行任何I/O的唯一时间是您正在读取文件.因此,这是程序中唯一需要使用IO
标记的部分.程序的其余部分可以"纯粹"编写.
听起来你需要的是文章The IO Monad For Mon Is Not Care,这应该解释你的很多问题.不要被术语"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)
Run Code Online (Sandbox Code Playgroud)
要回答npfedwards
关于应用lines
输出的注释readFile text.txt
,你需要意识到这readFile text.txt
给你一个IO String
,并且只有当你将它绑定到一个变量(使用contents <-
)时才能访问底层String
,以便你可以应用lines
它.
记住:一旦你离开IO
,你永远不会回去.
1我故意无视,unsafePerformIO
因为正如名称所暗示的那样,这是非常不安全的!除非你真的知道自己在做什么,否则不要使用它.
poo*_*a72 10
作为编程菜鸟,我也被IO
s 迷惑了.请记住,如果你去,IO
你永远不会出来.克里斯写了一个很好的解释原因.我只是想提供一些关于如何IO String
在monad中使用的例子.我将使用getLine读取用户输入并返回一个IO String
.
line <- getLine
Run Code Online (Sandbox Code Playgroud)
所有这一切都将用户输入绑定getLine
到一个名为的值line
.如果你在ghci中输入这个,并输入:type line
它将返回:
:type line
line :: String
Run Code Online (Sandbox Code Playgroud)
可是等等!getLine
返回一个IO String
:type getLine
getLine :: IO String
Run Code Online (Sandbox Code Playgroud)
所以,发生了什么事IO
,从尼斯getLine
?<-
发生了什么事.<-
是你的IO
朋友 它允许您显示IO
monad中受污染的值,并将其与正常功能一起使用.Monads易于识别,因为它们始于do
.像这样:
main = do
putStrLn "How much do you love Haskell?"
amount <- getLine
putStrln ("You love Haskell this much: " ++ amount)
Run Code Online (Sandbox Code Playgroud)
如果你像我一样,你很快就会发现这liftIO
是你最好的单身朋友,这$
有助于减少你需要写的括号数量.
那么你如何从中获取信息readFile
?好吧,如果readFile
输出是IO String
这样的:
:type readFile
readFile :: FilePath -> IO String
Run Code Online (Sandbox Code Playgroud)
那么你需要的只是你的友好<-
:
yourdata <- readFile "samplefile.txt"
Run Code Online (Sandbox Code Playgroud)
现在,如果在ghci中输入并检查yourdata
你的类型,你会发现它很简单String
.
:type yourdata
text :: String
Run Code Online (Sandbox Code Playgroud)
正如人们已经说过的,如果你有两个函数,一个是readStringFromFile :: FilePath -> IO String
,而另一个是doTheRightThingWithString :: String -> Something
,那么你真的不需要从中转义字符串IO
,因为你可以用各种方式组合这两个函数:
随着fmap
对IO
(IO
是Functor
):
fmap doTheRightThingWithString readStringFromFile
Run Code Online (Sandbox Code Playgroud)
随着(<$>)
对IO
(IO
为Applicative
和(<$>) == fmap
):
import Control.Applicative
...
doTheRightThingWithString <$> readStringFromFile
Run Code Online (Sandbox Code Playgroud)
使用liftM
for IO
(liftM == fmap
):
import Control.Monad
...
liftM doTheRightThingWithString readStringFromFile
Run Code Online (Sandbox Code Playgroud)
with (>>=)
for IO
(IO
is Monad
,fmap == (<$>) == liftM == \f m -> m >>= return . f
):
readStringFromFile >>= \string -> return (doTheRightThingWithString string)
readStringFromFile >>= \string -> return $ doTheRightThingWithString string
readStringFromFile >>= return . doTheRightThingWithString
return . doTheRightThingWithString =<< readStringFromFile
Run Code Online (Sandbox Code Playgroud)
用do
符号表示:
do
...
string <- readStringFromFile
-- ^ you escape String from IO but only inside this do-block
let result = doTheRightThingWithString string
...
return result
Run Code Online (Sandbox Code Playgroud)
每次你都会得到IO Something
.
你为什么要这样做呢?好吧,有了这个,您将拥有您的语言的纯粹和
引用透明的程序(功能).这意味着每个类型都是无IO的函数是纯粹的和引用透明的,因此对于相同的参数,它将返回相同的值.例如,对于doTheRightThingWithString
相同的返回Something
相同String
.但是readStringFromFile
,不是IO的,每次都可以返回不同的字符串(因为文件可以更改),这样就无法从中逃脱这样的不正确值IO
.
如果您有这种类型的解析器:
myParser :: String -> Foo
Run Code Online (Sandbox Code Playgroud)
然后您使用读取文件
readFile "thisfile.txt"
Run Code Online (Sandbox Code Playgroud)
然后您可以使用以下内容读取和解析文件
fmap myParser (readFile "thisfile.txt")
Run Code Online (Sandbox Code Playgroud)
结果将为type IO Foo
。
该fmap
方法myParser
在IO内部运行。
想起来的另一种方式是,然而myParser :: String -> Foo
,fmap myParser :: IO String -> IO Foo
。