我正在尝试学习Haskell的基础知识,同时为Pandoc开发一个过滤器,以递归方式包含其他降价文件。
根据脚本指南,我能够创建一个有点工作的过滤器。这将查找具有include
类的CodeBlocks,并尝试包含引用文件的AST。
```include
section-1.md
section-2.md
#pleasedontincludeme.md
```
可以在以下存储库中找到整个过滤器和输入源:steindani/pandoc-include(或参见下文)
可以使用过滤器运行pandoc,并使用以下命令以markdown格式查看输出:pandoc -t json input.md | runhaskell IncludeFilter.hs | pandoc --from json --to markdown
我注意到map
函数(at line 38) - 虽然获取要包含的文件列表 - 只调用第一个元素的函数。这不是唯一奇怪的行为。包含的文件也可以包含一个处理的包含块,并包含引用的文件;但它不会更深入,最后一个文件的包含块会被忽略。
为什么map函数不遍历整个列表?为什么它在2级层次结构后停止?
请注意,我刚刚开始学习Haskell,我确信我犯了错误,但我很乐意学习。
谢谢
完整源代码:
module Text.Pandoc.Include where
import Control.Monad
import Data.List.Split
import Text.Pandoc.JSON
import Text.Pandoc
import Text.Pandoc.Error
stripPandoc :: Either PandocError Pandoc -> [Block]
stripPandoc p =
case p of
Left _ -> [Null]
Right (Pandoc _ blocks) -> blocks
ioReadMarkdown :: String -> IO(Either PandocError Pandoc)
ioReadMarkdown content = return (readMarkdown def content)
getContent :: String -> IO [Block]
getContent file = do
c <- readFile file
p <- ioReadMarkdown c
return (stripPandoc p)
doInclude :: Block -> IO [Block]
doInclude cb@(CodeBlock (_, classes, _) list) =
if "include" `elem` classes
then do
files <- return $ wordsBy (=='\n') list
contents <- return $ map getContent files
result <- return $ msum contents
result
else
return [cb]
doInclude x = return [x]
main :: IO ()
main = toJSONFilter doInclude
答案 0 :(得分:6)
我可以在doInclude
函数中发现以下错误:
doInclude :: Block -> IO [Block]
doInclude cb@(CodeBlock (_, classes, _) list) =
if "include" `elem` classes
then do
let files = wordsBy (=='\n') list
let contents = map getContent files
let result = msum contents -- HERE
result
else
return [cb]
doInclude x = return [x]
由于整个函数的结果类型是IO [Block]
,我们可以向后工作:
result
的类型为IO [Block]
contents
的类型为[IO [Block]]
msum
正在使用类型[IO [Block]] -> IO [Block]
问题的第三部分是问题 - 在你的程序中,有一个非MonadPlus
实例加载IO
,我敢打赌它在msum contents
上做了什么是这样的:
这不是标准MonadPlus
实例,因此它来自您要导入的其中一个库。我不知道哪个。
这里的一般建议是:
因为这里的问题似乎是msum
的使用类型与您期望的类型不同。通常这会产生类型错误,但在这里你运气不好,它与某个库中的一个奇怪的类型类实例进行了交互。
根据评论,您对msum contents
的意图是创建一个IO
操作,按顺序执行所有子操作,并将其结果作为列表收集。好吧,the MonadPlus
class isn't normally defined for IO
, and when it is it does something else。所以这里使用的正确函数是sequence
:
-- Simplified version, the real one is more general:
sequence :: Monad m => [m a] -> m [a]
sequence [] = return []
sequence (ma:mas) = do
a <- ma
as <- mas
return (a:as)
这会让您从[IO [Block]]
转到IO [[Block]]
。要消除双重嵌套列表,您只需使用fmap
在concat
内应用IO
。