我目前正在实施Lexer / Parser。有一件事让我感到困惑的是,目前我Parser.hs中的一半代码将专门用于获取单个令牌:
对于像这样的小数据类型:
data Tok
= IdLower String
| IdUpper String
| IdSymbol String
| IdColon String
| Equals
| Newline
我似乎需要这样的东西:
idLower :: Parser String
idLower = get >>= \s -> if
| (_, IdLower n) :- xs <- s -> put xs *> pure n
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
idUpper :: Parser String
idUpper = get >>= \s -> if
| (_, IdUpper n) :- xs <- s -> put xs *> pure n
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
idSymbol :: Parser String
idSymbol = get >>= \s -> if
| (_, IdSymbol n) :- xs <- s -> put xs *> pure n
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
idColon :: Parser String
idColon = get >>= \s -> if
| (_, IdColon n) :- xs <- s -> put xs *> pure n
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
equals :: Parser ()
equals = get >>= \s -> if
| (_, Equals) :- xs <- s -> put xs
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
newline :: Parser ()
newline = get >>= \s -> if
| (_, Newline) :- xs <- s -> put xs
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
这就像99%的重复代码,它们之间唯一的区别就是使用的构造函数,以及我是否有类似pure n
的内容。
我尝试过重构一下,以便每个符号只有一个Tok -> Maybe ()
或Tok -> Maybe String
函数,然后创建一个更高阶函数,将这些函数作为参数。但是每个Tok -> Maybe a
函数需要3行加上1行spacer,现在我需要另一个更高阶函数来支持它,如果我想要shorthands,那么我可以使用idLower
而不是{{1然后我最终会得到尽可能多的代码,如果不是更多的话!
我真的希望有上述的替代品。现在我知道我可以通过创建一个自动失败的功能来减少一些重复,如果第一个后卫没有击中,它总是会调用我可以推迟的相关getToken idLower
,但即便如此还是感觉很糟糕。
答案 0 :(得分:5)
您可以使用棱镜(例如来自lens library)免费获取Tok -> Maybe ()
和Tok -> Maybe String
函数“(通过模板Haskell)。
data Tok =
IdLower String
| IdUpper String
| IdSymbol String
| IdColon String
| Equals
| Newline
makePrisms ''Tok
现在你可以说:
GHCi> preview _IdLower (IdLower "foo")
Just "foo"
GHCi> preview _IdLower (IdUpper "Foo")
Nothing
然后,正如您自己建议的那样,您可以在特定于令牌的函数中从棱镜中抽象出来:
tok :: Prism' Tok a -> Parser a
tok p = get >>= \ s -> if
| (_, t) :- xs <- s, Just n <- preview p t -> put xs *> pure n
| (l, t) :- _ <- s -> throwError [(l, "Unexpected " <> description t)]
| Nil l <- s -> throwError [(l, "Unexpected end of input")]
然后,您可以通过说tok _IdLower
或tok _Equals
来恢复单个功能。
答案 1 :(得分:2)
这是一种不同的方法,也需要一些样板代码,但没有模板Haskell。
它基于重构您的Milk was selected!
Bread was selected!
>> Script Ended
类型,以便您可以使用一种形式的相等测试而不是匹配,并且可以统一提取令牌的有效负载:
Tok
每个令牌都有令牌类型和有效负载。令牌类型是GADT 确定有效载荷的类型:
data Tok where
Tok :: TokKind a -> a -> Tok
我们现在需要一种相等的形式,这意味着如果两个令牌具有相同的类型,则它们的有效载荷类型必须是兼容的。这是来自data TokKind :: * -> * where
IdLower :: TokKind String
IdUpper :: TokKind String
IdSymbol :: TokKind String
IdColon :: TokKind String
Equal :: TokKind ()
Newline :: TokKind ()
的{{1}}(遗憾的是,目前无法以任何简单的方式导出,除非再次使用TH):
testEquality
然后您的参数化标记函数变为
Data.Type.Equality