如何在Haskell中解析IO String?

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是一种非常有原则的语言.它试图保持"纯"函数(没有任何副作用,并在给出相同的输入时始终返回相同的结果)和"不纯"函数(具有类似读取文件,打印等副作用)之间的区别到屏幕,写入磁盘等).规则是:

  1. 您可以在任何地方使用纯函数(在其他纯函数中,或在不纯函数中)
  2. 您只能在其他不纯函数中使用不纯函数.

代码标记为纯或不纯的方式是使用类型系统.当你看到像这样的函数签名时

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动作中获取值?幸运的是,函数的类型mainIO ()(它是一个执行某些I/O并返回的操作(),与返回任何内容相同).所以你总是可以在IO里面使用你的功能main.当您执行Haskell程序时,您正在执行的是运行该main函数,这会导致程序定义中的所有I/O实际执行 - 例如,您可以从文件读取和写入,请求用户输入,写入到stdout等等

您可以考虑构造一个像这样的Haskell程序:

  • 所有执行I/O的IO代码都会获得标记(基本上,你把它放在一个do块中)
  • 不需要执行I/O的代码不需要在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换行符,所以现在你有一个Strings 列表,每个列表对应一个文件行.功能

init :: [a] -> [a]
Run Code Online (Sandbox Code Playgroud)

从列表中删除最后一个元素(这将消除.每行的最后一个元素).功能

read :: (Read a) => String -> a
Run Code Online (Sandbox Code Playgroud)

将a String转换为任意Haskell数据类型,例如IntBool.合理地结合这些功能将为您提供程序.

请注意,实际需要执行任何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因为正如名称所暗示的那样,这是非常不安全的!除非你真的知道自己在做什么,否则不要使用它.

  • 我觉得我们甚至不应该提到*在这些答案中不会被命名*的功能,如果只是因为Haskell的一些学生可能会看到它,看看类型定义并简单地说"*啊哈!这就是什么我一直都在玩!*" (5认同)

poo*_*a72 10

作为编程菜鸟,我也被IOs 迷惑了.请记住,如果你去,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朋友 它允许您显示IOmonad中受污染的值,并将其与正常功能一起使用.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)

  • @matthias `getLine &gt;&gt;= (\amount -&gt; ...)`。 (2认同)

JJJ*_*JJJ 8

正如人们已经说过的,如果你有两个函数,一个是readStringFromFile :: FilePath -> IO String,而另一个是doTheRightThingWithString :: String -> Something,那么你真的不需要从中转义字符串IO,因为你可以用各种方式组合这两个函数:

随着fmapIO(IOFunctor):

fmap doTheRightThingWithString readStringFromFile
Run Code Online (Sandbox Code Playgroud)

随着(<$>)IO(IOApplicative(<$>) == fmap):

import Control.Applicative

...

doTheRightThingWithString <$> readStringFromFile
Run Code Online (Sandbox Code Playgroud)

使用liftMfor IO(liftM == fmap):

import Control.Monad

...

liftM doTheRightThingWithString readStringFromFile
Run Code Online (Sandbox Code Playgroud)

with (>>=)for IO(IOis 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.


dav*_*420 5

如果您有这种类型的解析器:

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 -> Foofmap myParser :: IO String -> IO Foo