我目前正在尝试研究和了解attoparsec
库的源代码,但有些细节我自己无法弄清楚。例如,Parser
类型的定义:
newtype Parser i a = Parser {
runParser :: forall r.
State i -> Pos -> More
-> Failure i (State i) r
-> Success i (State i) a r
-> IResult i r
}
newtype Pos = Pos { fromPos :: Int }
deriving (Eq, Ord, Show, Num)
data IResult i r =
Fail i [String] String
| Partial (i -> IResult i r)
| Done i r
type Failure i t r = t -> Pos -> More -> [String] -> String
-> IResult i r
type Success i t a r = t -> Pos -> More -> a -> IResult i r
我还不完全理解的是类型参数r
的用法。如果我像这样定义runParser
的类型签名会有什么不同:
State i -> Pos -> More -> Failure i (State i) a -> Success i (State i) a a -> IResult i a
是否可以帮助我了解forall r.
在这种情况下的确切含义以及为什么有必要在runParser
的类型签名中使用它?
提前很多!
更新:进一步澄清我的问题:我目前无法理解的是为什么有必要首先引入类型参数r
。可以想象,Parser
类型也可以这样定义:
newtype Parser i a = Parser {
runParser ::
State i -> Pos -> More
-> Failure i (State i) a
-> Success i (State i) a
-> IResult i a
}
data IResult i a =
Fail i [String] String
| Partial (i -> IResult i a)
| Done i a
type Failure i t a = t -> Pos -> More -> [String] -> String
-> IResult i a
type Success i t a = t -> Pos -> More -> a -> IResult i a
其中根本不使用类型参数r
。我的问题是为什么这个定义会“错误”以及它会带来什么问题......
答案 0 :(得分:2)
attoparsec创建连续传递样式(CPS)解析器
没有forall
,我们就无法连锁
解析器在一起。
这是一个大大简化的版本
涉及的类型和bindP
的定义 - monadic绑定运算符。
我们已经消除了故障延续和输入源。
{-# LANGUAGE Rank2Types #-}
type IResult r = r
type Success a r = a -> IResult r -- Success a r == a -> r
newtype Parser a = Parser {
runParser :: forall r. Success a r
-> IResult r
}
bindP :: Parser a -> (a -> Parser b) -> Parser b
bindP m g =
Parser $ \ks -> runParser m $ \a -> runParser (g a) ks
-----
-----
注意Success a r
只是函数类型a -> r
。
如果我们将runParser
的定义替换为:
runParser :: Success a a -> IResult a
我们会在上面带下划线的位置收到类型错误。 要理解这一点,可以计算出以下类型:
ks :: Success b b
runParser m $ \a -> runParser (g a) ks :: IResult b
\a -> runParser (g a) ks :: Success b b == b -> b
a :: b
但是从表达式(g a)
我们也可以得出结论a
的类型为a
这给了我们类型错误。
基本上Parser a
可以被认为是一种方法(或计算)
生成a
和runParser p ks
类型值的方法是
获取该值并将其提供给需要a
的函数。
对于任何ks
,延续函数a -> r
可以包含r
类型
唯一的要求是其输入类型为a
。
通过在定义Success a a
时使用runParser
,我们限制了runParser
a -> a
对runParser
类型函数的适用性。这就是我们的原因
想要将runParser :: Parser a -> (a -> r) -> r
定义为:
{{1}}
这种CPS风格是一种非常不同的解析方法 在Monadic Parsing in Haskell
答案 1 :(得分:0)
forall r
部分表示此类型适用于所有r
作为结果,而无需在newtype
声明的左侧指定。如Haskell wikibook中所述:
forall
关键字用于将类型变量显式引入范围。
在使用r
之前,forall
类型变量不在范围内。那个wikibook页面用几个例子很好地解释了一些事情,所以我鼓励你去看看,但文章的关键是forall
就像一个类型的交集,所以如果你把类型视为集合元素,Bool = {False, True, ⊥}
,Int = {minBound..maxBound, ⊥}
,Char = {minBound..maxBound, ⊥}
等(根据需要展开{{1}},minBound..maxBound
表示未定义,通常称为底部),然后< / p>
⊥
是所有类型的交集,即forall a. a
,而
{⊥}
是受forall a. Show a => a
约束的所有类型的交集。在这种情况下,当您有类似
Show
然后构造函数data T = forall a. MkT a
具有类型MkT
,允许您将任何类型转换为forall a. a -> T
,这个技巧允许您拥有异构列表:
T
但是你不能用这种类型做什么,你怎么打开[MkT (), MkT 1, MkT "hello"] :: [T]
用它来做任何事情?您可以在构造函数上进行模式匹配,但是您只需要一个MkT
类型的值,而没有关于它的信息。如果你有
a
然后你可以做类似
的事情data S = forall s. Show s => MkS s
在attoparsec的情况下,它有点类似于-- Pass through instance of Show
instance Show S where show (MkS s) = show s
map show [MkS "hello", MkS 5, MkS (Just 2)]
["\"hello\"", "5", "Just 2"]
monad,它使用ST
来阻止你编写非法操作。同样,那篇wikibooks文章提供了一个很好的解释。如果不够,请发表评论,我会看看是否可以澄清。