如何在Haskell中将文件读取为字符串?

时间:2018-02-08 14:33:37

标签: haskell

我是Haskell的初学者。我尝试使用markdown库转换mardown并将其插入由Blaze制作的网页中。我可以这样做:

readme :: Html
readme = do
  markdown def "#test"

哪个工作正常。但我不能这样做:

readme :: Html
readme = do
  readmeFile <- readFile "../README.md"
  markdown def readmeFile

因为我收到错误:

Views/Home.hs:55:17: error:
    • Couldn't match type ‘IO’ with ‘Text.Blaze.Internal.MarkupM’
      Expected type: Text.Blaze.Internal.MarkupM String
        Actual type: IO String
    • In a stmt of a 'do' block: readmeFile <- readFile "../README.md"
      In the expression:
        do { readmeFile <- readFile "../README.md";
             markdown def readmeFile }
      In an equation for ‘readme’:
          readme
            = do { readmeFile <- readFile "../README.md";
                   markdown def readmeFile }

Views/Home.hs:56:16: error:
    • Couldn't match type ‘[Char]’ with ‘Data.Text.Internal.Lazy.Text’
      Expected type: Data.Text.Internal.Lazy.Text
        Actual type: String
    • In the second argument of ‘markdown’, namely ‘readmeFile’
      In a stmt of a 'do' block: markdown def readmeFile
      In the expression:
        do { readmeFile <- readFile "../README.md";
             markdown def readmeFile }

我知道有一些简单的方法可以解决这个问题,但我不知道从哪里开始。 Here's the rest of my script,如果它有助于将其置于语境中。 (它使用OverloadedStrings。)

2 个答案:

答案 0 :(得分:5)

如果您声明类型readme,则表示mkReadme :: IO Html mkReadme = do readmeFile <- readFile "../README.md" return $ markdown def readmeFile 将是纯值,即适当的常量。因此,每次运行程序时保证都是一样的 - 很好的保证,但这也意味着你不能让它依赖于外部文件,这可能随时都会发生变化。

所以你想要的可能就是:

readFile

几乎起作用。不完全是因为String生成了一个过时的markdown,而Text想要更高效的readFile类型。易于修复:只需使用text库中相应的import qualified Data.Text.Lazy.IO as Txt ... mkReadme :: IO Html mkReadme = do readmeFile <- Txt.readFile "../README.md" return $ markdown def readmeFile 函数。

{{1}}

答案 1 :(得分:1)

你已经接受了答案,但是你在评论中提出了一个跟进问题,并且leftroundabout比我更了解这个网站,建议我回复一个答案。

您询问如何在程序中使用类型为mkReadme的{​​{1}}的结果。第一次遇到同样的问题时,这让我陷入了一个循环,并且该程序因此而变得非常混乱。这是我想出来的。

你可以考虑IO Html monad的一种方式 - 不是它的工作方式的详细信息,而是它存在的原因,以及何时需要它 - 它包含来自外部世界的结果,因此需要同步。所以IO需要从外部世界读取,其结果是readFile,然后IO Text依赖于该结果并返回markdown解析树,因此结果取决于来自外部的信息,需要是Html。然后,取决于该结果的任何结果最终都是IO Html某事物或其他事物,并且monad在整个程序中传播。

所以这就是你如何阻止这种情况发生并将其限制在IO。这是我们可以添加到leftroundabout答案的main例程:

main

{- Needs: text, markdown -} import qualified Data.Text.Lazy.IO as TIO import Text.Blaze.Html (Html) import Text.Blaze.Html.Renderer.Text (renderHtml) import Text.Markdown (def, markdown) mkReadme :: IO Html mkReadme = do readmeFile <- TIO.readFile "../README.md" return $ markdown def readmeFile main :: IO () main = do htmlVersion <- mkReadme -- Type signature is :: IO Html let plainText = renderHtml htmlVersion -- Type is :: Html -> Text TIO.putStrLn plainText -- Type signature is :: Text -> IO () 的第一行绑定main变量。第二个在纯函数中使用它,接受IO Html并返回Html,实际上提升到行之间的Text monad,这要归功于IO符号。第三个将do传递给一个带Text并返回Text的函数。

如果需要将纯函数转换为monadic函数,请使用IO (),如果需要将纯变量包装为monadic值,请使用Control.Monad.LiftM

如果你想知道编译器到底在做什么,这是另一种编写它的方法:

return

每个main = mkReadme >>= return . renderHtml >>= TIO.putStrLn 运算符将包含在>>= monad中的上一个函数的输出传递给链中的下一个函数。 IO将纯函数的输出包装在return monad中,以便它可以绑定。

该程序将转换后的Markdown文件作为HTML转储到标准输出。将最后一行替换为您要从IO返回的Html值所做的任何操作,并且可以假装它不是Markdown