我试图弄清楚如何以最好的方式解析Haskell中的sum-datatype。这是我尝试的摘录
type Value = Int
data Operator = ADD | SUB | MUL | DIV | SQR deriving (Show)
toOperator :: String -> Maybe Operator
toOperator "ADD" = Just ADD
toOperator "SUB" = Just SUB
toOperator "MUL" = Just MUL
toOperator "DIV" = Just DIV
toOperator "SQR" = Just SQR
toOperator _ = Nothing
parseOperator :: ParsecT String u Identity () Operator
parseOperator = do
s <- choice $ map (try . string) ["ADD", "SUB", "MUL", "DIV", "SQR"]
case toOperator s of
Just x -> return x
Nothing -> fail "Could not parse that operator."
此代码执行我想要的但有一个明显的问题:它检查数据两次。进入choice $ map (try . string) ["ADD", "SUB", "MUL", "DIV", "SQR"]
行,进入toOperator
一次。
我想要的是,如果字符串出现在列表中,则希望将字符串解析为Operator
,否则将失败。但我无法弄清楚如何以“干净”的方式做到这一点。
答案 0 :(得分:3)
如果让toOperator
直接参与Parsec解析过程,而不是让它成为单独发生的步骤,那就更简单了,因为“这个是否是一个有效的运算符”可以为解析过程提供反馈
对于这种特殊情况,你正在解析的东西是零字段枚举,其构造函数名称与你正在解析的字符串完全匹配,已经发布了几个好的快捷方式,向你展示如何简洁地解析这些构造函数。在这个答案中,我将展示一个替代方法,它更容易适应“匹配几个案例之一”的一般情况,并处理更高级的东西,如“三个构造函数中的一个有一个Int参数但其他人没有。“
operator :: StringParser Operator
operator = string "ADD" *> pure ADD
<|> string "DIV" *> pure DIV
<|> string "MUL" *> pure MUL
<|> try (string "SUB") *> pure SUB
<|> string "SQR" *> pure SQR
现在假设您有一个额外的构造函数VAR
,它接受一个String参数。可以很容易地将该构造函数的支持添加到此解析器中:
operator :: StringParser Operator
operator = ...
<|> string "VAR" *> (VAR <$> var)
var :: StringParser String
var = spaces *> anyChar `manyTill` space
答案 1 :(得分:2)
您有几种方法可以避免此类重复。
首先,如果您尝试解析的输入中显示的名称与Operator
的构造函数完全匹配(在您的示例中似乎就是这种情况),则可以完全避免使用toOperator
通过导出Read
的{{1}}实例,并使用Operator
。然后代码将沿着
read
您必须小心在此处列出与parseOperator :: ParsecT String u Identity () Operator
parseOperator = do
s <- choice $ map (try . string) ["ADD", "SUB", "MUL", "DIV", "SQR"]
pure $ read s
构造函数相同的名称,并根据需要更新它们。
其次,您可以通过定义列表(或Operator
或Data.Map
)来自己构建映射,然后使用它来指定可接受的输入并找到相应的运算符构造函数:
HashMap
注意operators :: [(String, Operator)]
operators = [("ADD", ADD), ("SUB", SUB), ("MUL", MUL), ("DIV", DIV), ("SQR", SQR)]
parseOperator :: ParsecT String u Identity () Operator
parseOperator = do
s <- choice $ map (try . string . fst) operators
case lookup s operators of
Just x -> return x
Nothing -> fail "Could not parse that operator."
对于定义良好的解析器并不是必需的:根据定义,解析的结果将在case
列表中。而且,缺点是你必须保持operators
和构造函数列表同步。
第三个,也许是最甜蜜的一个是通过一些额外的类型类自动生成运算符列表:operators
和Bounded
,它们组合在一起,允许枚举所有构造函数类似于你的类型,以及哪些ghc会愉快地为你的Enum
派生。然后Operator
定义看起来像
operators
答案 2 :(得分:1)
您只需要toOperator
的倒数来映射解析器; read
是一个简单的(如果不是强大的)示例。
>>> data Operator = ADD | SUB | MUL | DIV | SQR deriving (Show, Read)
>>> parse (read <$> string "ADD") "" "ADD" :: Either ParseError Operator
Right ADD