Haskell映射不会遍历整个列表

时间:2015-12-01 22:12:25

标签: haskell dictionary io monads pandoc

我正在尝试学习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

1 个答案:

答案 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],我们可以向后工作:

  1. result的类型为IO [Block]
  2. contents的类型为[IO [Block]]
  3. msum正在使用类型[IO [Block]] -> IO [Block]
  4. 问题的第三部分是问题 - 在你的程序中,有一个非MonadPlus实例加载IO,我敢打赌它在msum contents上做了什么是这样的:

    • 执行第一个操作
      • 如果成功,则产生与此相同的结果,丢弃列表的其余部分。 (这是你观察到的行为的原因。)
      • 如果失败并发生异常,请尝试列表的其余部分。

    这不是标准MonadPlus实例,因此它来自您要导入的其中一个库。我不知道哪个。

    这里的一般建议是:

    1. 将程序拆分为较小的功能
    2. 为这些功能编写类型签名
    3. 因为这里的问题似乎是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]]。要消除双重嵌套列表,您只需使用fmapconcat内应用IO