我如何解析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程序:

  • 所有执行I / O的代码都会获得IO标记(基本上,你把它放在一个do块中)
  • 不需要执行I / O的代码不需要在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数据类型,例如IntBool 。 合理组合这些功能将为您提供程序。

    请注意,您实际需要执行任何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
    

    那么来自getLineIO发生了什么? <-发生了什么事。 <-是你的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 fmapIOFunctor ):

    fmap doTheRightThingWithString readStringFromFile
    

    使用IO (<$>)IOApplicative(<$>) == fmap ):

    import Control.Applicative
    
    ...
    
    doTheRightThingWithString <$> readStringFromFile
    

    liftM for IOliftM == fmap ):

    import Control.Monad
    
    ...
    
    liftM doTheRightThingWithString readStringFromFile
    

    (>>=)表示IOIOMonadfmap == (<$>) == 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转义这种不确定的值。

    链接地址: http://www.djcxy.com/p/43051.html

    上一篇: How can I parse the IO String in Haskell?

    下一篇: GHC type inference woes