Haskell 映射不会迭代整个列表

标签 haskell dictionary io monads pandoc

我正在尝试学习 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 function ( at line 38 ) — 尽管获取要包含的文件列表 — 只调用第一个元素的函数。这并不是唯一奇怪的行为。被包含的文件还可以有一个被处理的包含 block 并且被引用的文件被包含;但它不会更深入,最后一个文件的包含 block 将被忽略。

为什么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

最佳答案

我可以在您的 doIninclude 函数中发现以下错误:

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. 结果的类型为IO [Block]
  2. contents 的类型为 [IO [Block]]
  3. msum[IO [Block]] -> IO [Block] 类型一起使用

第三部分就是问题所在——不知何故,在你的程序中,有一个非标准的 MonadPlus 实例正在为 IO 加载,我敢打赌它会做什么msum 内容 是这样的:

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

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

这里的一般建议是:

  1. 将程序拆分为更小的函数
  2. 为这些函数编写类型签名

因为这里的问题似乎是 msum 所使用的类型与您期望的类型不同。通常这会产生类型错误,但在这里你很不幸,它与某些库中的奇怪类型类实例交互。

<小时/>

根据评论,您使用 msum content 的目的是创建一个 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]]。要消除双重嵌套列表,只需使用 fmapIO 内应用 concat 即可。

关于Haskell 映射不会迭代整个列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34031192/

相关文章:

c++ - 我可以通过整数索引访问 C++ std::map 中的元素吗?

c++ - 在同一列表的 while 循环中迭代 for 循环所需的 Big-O 时间

io - 似乎无法从运行中获取非字符串输出

haskell - 什么都不过滤,只解包

haskell - 如何让Haskell程序响应用户输入显示初步结果?

haskell - GHC 类型函数和作用域类型变量

java - 它们是完全相同的方法,但一种有效,另一种无效

haskell - 如何 - 使用 emacs - haskell 模式将一个程序的输出通过管道传输到另一个程序(capslocker)的输入?

python检查字典是否在列表中

php - 使用 fseek 逐行读取文件