Haskell map 不会遍历整个列表
Haskell map does not iterate over the whole list
我正在尝试学习 Haskell 的基础知识,同时为 Pandoc 开发过滤器以递归地包含其他降价文件。
根据脚本指南,我能够创建一个有点工作的过滤器。这将查找带有 include
class 的代码块,并尝试包含引用文件的 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
我可以在您的 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
加载,我敢打赌它在 [=22] 上的作用=] 是这样的:
- 执行第一个动作
- 如果成功,产生与那个相同的结果并丢弃列表的其余部分。 (这是您观察到的行为的原因。)
- 如果失败并出现异常,请尝试列表的其余部分。
这不是标准 MonadPlus
实例,因此它来自您正在导入的库之一。不知道是哪个。
这里的一般建议是:
- 将您的程序拆分成更小的函数
- 为这些函数编写类型签名
因为这里的问题似乎是 msum
与您预期的类型不同。通常这会产生类型错误,但在这里你运气不好,它与某个库中的奇怪类型 class 实例交互。
根据评论,您 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
在 IO
.
中应用 concat
我正在尝试学习 Haskell 的基础知识,同时为 Pandoc 开发过滤器以递归地包含其他降价文件。
根据脚本指南,我能够创建一个有点工作的过滤器。这将查找带有 include
class 的代码块,并尝试包含引用文件的 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
我可以在您的 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
加载,我敢打赌它在 [=22] 上的作用=] 是这样的:
- 执行第一个动作
- 如果成功,产生与那个相同的结果并丢弃列表的其余部分。 (这是您观察到的行为的原因。)
- 如果失败并出现异常,请尝试列表的其余部分。
这不是标准 MonadPlus
实例,因此它来自您正在导入的库之一。不知道是哪个。
这里的一般建议是:
- 将您的程序拆分成更小的函数
- 为这些函数编写类型签名
因为这里的问题似乎是 msum
与您预期的类型不同。通常这会产生类型错误,但在这里你运气不好,它与某个库中的奇怪类型 class 实例交互。
根据评论,您 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
在 IO
.
concat