如果我有一个解析器a : Parser A
和一个解析器b : Parser B
,那么我可以将它组合成一个解析器a | b : Parser (Either A B)
。当你开始添加更多替代方案并获得类似Either A (Either B C)
的类型时,这会有点麻烦。我可以想象将之前的类型扁平化为Alternative A B C
之类的东西。是否有我可以执行的标准转换,或者我是否为Alternative A B C ...
等类型生成了大量样板文件。
答案 0 :(得分:8)
关于Either
的有趣之处在于您可以将其用作类型级cons
运算符。
A `Either` (B `Either` (C `Either` (D `Either` Void))) --> [A,B,C,D]
所以我们需要做的就是明确这一点。您需要ghc-7.8来支持封闭数据系列:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
-- ...
type family OneOf (as :: [*]) :: * where
OneOf '[a] = a
OneOf (a ': as) = Either a (OneOf as)
现在,您可以更简洁地编写类型:
aorborc :: Parser (OneOf '[A, B, C])
aorborc = a | (b | c)
它仍然是Either
,因此您仍然可以轻松地与使用Either
的所有现有代码进行互操作,这很不错。
答案 1 :(得分:3)
在Haskell中只有一种可能的和类型,并且由于现成的类实例和辅助函数在许多情况下都很有用,但是当你嵌套它时变得相当笨拙。
解析器的最佳方法是创建自己的数据类型,该数据类型反映您正在解析的结构并直接解析为该结构。让我们举一个关于玩具语言的部分玩具示例。
data Statement = TypeDec String Type
DataDec String [Constructor]
FunctionDec String LambdaExpression
statement :: Parser Statement
statement = TypeDec <$> string "type " *> identifier <*> string " = " *> type
<|> DataDec <$> string "data " *> identifier <*> string " = " *> many constructor
<|> FunctionDec <$> identifier <*> string " = " *> lambdaExpression
通过这种方式,您的数据结构和代码都会镜像您正在解析的语法中的作品。这样做的最大好处是您的数据类型安全,清晰,并且可以在解析后立即使用。
(我永远不会记住*>
和<*
的固定性,所以我可能就像你需要括号或其他东西一样,但希望你能得到这个想法。)< /子>