生成解析器,该解析器在另一个解析器的输出上运行接收到的解析器,并以单子方式连接结果
Generate parser that runs a received parser on the output of another parser and monadically joins the results
给定以下类型和函数,用于将 CSV 字段的字段解析为字符串:
type Parser resultType = ParsecT String () Identity resultType
cell :: Parser String
我实现了以下功能:
customCell :: String -> Parser res -> Parser res
customCell typeName subparser =
cell
>>= either (const $ unexpected typeName)
return . parse (subparser <* eof) ""
虽然我一直在想我没有按照需要尽可能多地使用 Monad 概念,但最终会有更好的方法将内部解析器的结果与外部解析器合并,特别是关于它的失败。
有人知道我该怎么做吗,或者这段代码是用来做什么的?
PS - 我现在意识到我的类型简化可能不合适,也许我想要的是用 Either Monad 替换底层的 Identity Monad....不幸的是,我觉得还不够还熟悉 monad 转换器。
PS2 - 底层的 monad 到底有什么用?
遗憾的是,我知道 Haskell 没有解析器库或解析器生成器支持像这样的垂直解析器组合。像你写的东西一样好。该死!
详细说明@Daniel Wagner 的回答...通常使用 Parsec 构建解析器的方式是,您从解析特定字符(例如加号或数字)的低级解析器开始,然后构建解析器在它们之上使用组合器(例如 many1
组合器,它将读取单个数字的解析器转换为读取一个或多个数字的解析器,或者一个单子解析器,解析器 "one or more digits" 后跟 "plus sign" 然后是 "one or more digits").
但是,每个解析器,无论是低级数字解析器还是高级 "addition expression" 解析器,都旨在直接应用于同一输入流。
您 通常 不做的是编写一个解析器,它吞噬输入流的一部分以生成,比方说,一个 String
和另一个解析器来解析String
(而不是原始输入流)并尝试组合它们。这是 Parsec 不直接支持的 "vertical composition" 类型,看起来不自然且非一元化。
正如评论中所指出的,在一些 情况下,垂直组合是最干净的整体方法(例如当您将一种语言嵌入到另一种语言的组件或表达式中时),但这不是 Parsec 解析器通常采用的方法。
您的应用程序的底线是,仅生成 String
的 cell
解析器过于专业化,无法使用。 CSV 文件更有用的 Parsec 框架是:
import Text.Parsec
import Text.Parsec.String
-- | `csv cell` parses a CSV file each of whose elements is parsed by `cell`
csv :: Parser a -> Parser [[a]]
csv cell = many (row cell)
-- | `row cell` parses a newline-terminated row of comma separated
-- `cell`-expressions
row :: Parser a -> Parser [a]
row cell = sepBy cell (char ',') <* char '\n'
现在,您可以编写一个自定义单元格解析器来解析正整数:
customCell :: Parser Int
customCell = read <$> many1 digit
并解析 CSV 文件:
> parse (csv customCell) "" "1,2,3\n4,5,6\n"
Right [[1,2,3],[4,5,6]]
>
在这里,cell
子解析器没有明确地将逗号分隔的单元格解析为字符串以提供给不同的解析器,"cell" 是一个隐式上下文,其中提供了一个单元格解析器被调用以在适当的位置解析基础输入流,在该位置人们期望在输入流中间的一行中间有一个逗号分隔的单元格。
给定以下类型和函数,用于将 CSV 字段的字段解析为字符串:
type Parser resultType = ParsecT String () Identity resultType
cell :: Parser String
我实现了以下功能:
customCell :: String -> Parser res -> Parser res
customCell typeName subparser =
cell
>>= either (const $ unexpected typeName)
return . parse (subparser <* eof) ""
虽然我一直在想我没有按照需要尽可能多地使用 Monad 概念,但最终会有更好的方法将内部解析器的结果与外部解析器合并,特别是关于它的失败。
有人知道我该怎么做吗,或者这段代码是用来做什么的?
PS - 我现在意识到我的类型简化可能不合适,也许我想要的是用 Either Monad 替换底层的 Identity Monad....不幸的是,我觉得还不够还熟悉 monad 转换器。
PS2 - 底层的 monad 到底有什么用?
遗憾的是,我知道 Haskell 没有解析器库或解析器生成器支持像这样的垂直解析器组合。像你写的东西一样好。该死!
详细说明@Daniel Wagner 的回答...通常使用 Parsec 构建解析器的方式是,您从解析特定字符(例如加号或数字)的低级解析器开始,然后构建解析器在它们之上使用组合器(例如 many1
组合器,它将读取单个数字的解析器转换为读取一个或多个数字的解析器,或者一个单子解析器,解析器 "one or more digits" 后跟 "plus sign" 然后是 "one or more digits").
但是,每个解析器,无论是低级数字解析器还是高级 "addition expression" 解析器,都旨在直接应用于同一输入流。
您 通常 不做的是编写一个解析器,它吞噬输入流的一部分以生成,比方说,一个 String
和另一个解析器来解析String
(而不是原始输入流)并尝试组合它们。这是 Parsec 不直接支持的 "vertical composition" 类型,看起来不自然且非一元化。
正如评论中所指出的,在一些 情况下,垂直组合是最干净的整体方法(例如当您将一种语言嵌入到另一种语言的组件或表达式中时),但这不是 Parsec 解析器通常采用的方法。
您的应用程序的底线是,仅生成 String
的 cell
解析器过于专业化,无法使用。 CSV 文件更有用的 Parsec 框架是:
import Text.Parsec
import Text.Parsec.String
-- | `csv cell` parses a CSV file each of whose elements is parsed by `cell`
csv :: Parser a -> Parser [[a]]
csv cell = many (row cell)
-- | `row cell` parses a newline-terminated row of comma separated
-- `cell`-expressions
row :: Parser a -> Parser [a]
row cell = sepBy cell (char ',') <* char '\n'
现在,您可以编写一个自定义单元格解析器来解析正整数:
customCell :: Parser Int
customCell = read <$> many1 digit
并解析 CSV 文件:
> parse (csv customCell) "" "1,2,3\n4,5,6\n"
Right [[1,2,3],[4,5,6]]
>
在这里,cell
子解析器没有明确地将逗号分隔的单元格解析为字符串以提供给不同的解析器,"cell" 是一个隐式上下文,其中提供了一个单元格解析器被调用以在适当的位置解析基础输入流,在该位置人们期望在输入流中间的一行中间有一个逗号分隔的单元格。