我是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。)
答案 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
。