我正在尝试学习Haskell的基础知识,同时为Pandoc开发一个过滤器,以递归方式包含其他markdown文件.
基于脚本指南,我能够创建一个有点工作的过滤器.这将查找带有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
函数(在第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
Luis Casilla.. 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
按顺序执行所有子动作的动作,并将其结果作为列表收集.好吧,这个MonadPlus
类通常IO
不是为它定义的,当它正在做的时候.所以这里使用的正确函数是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
我可以在你的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
按顺序执行所有子动作的动作,并将其结果作为列表收集.好吧,这个MonadPlus
类通常IO
不是为它定义的,当它正在做的时候.所以这里使用的正确函数是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