我使用parsec将一些源代码解析为AST。我最近打开了-Wall
和-W
选项,以便抓住"可疑"代码,它抱怨许多the parsec-related top-level functions in this file没有明确的类型扩展。
vimL = choice [ block
, statement
]
这里的推断类型是:
vimL :: ParsecT String () Data.Functor.Identity.Identity Node
因此,如果我添加该注释,编译器会抱怨无法访问Data.Functor.Identity.Identity
,这意味着我必须import
:
import Data.Functor.Identity
如果我这样做,我可以将类型注释简化为:
vimL :: ParsecT String () Identity Node
并且编译器仍然会接受它。但它仍然不是我非常理解的东西。
link = Link <$> (bar *> linkText <* bar)
where
bar = char '|'
linkText = many1 $ noneOf " \t\n|"
这里的推断类型是:
link :: forall u.
ParsecT String u Data.Functor.Identity.Identity Node
但我不能使用它,除非我也使用:
{-# LANGUAGE RankNTypes #-}
请注意,如果我放弃forall
,我可以省去。这两项工作都是:
link :: ParsecT String u Data.Functor.Identity.Identity Node
link :: ParsecT String u Identity Node
string' s = mapM_ char' s >> pure s <?> s
这个推断类型是:
string' :: forall s u (m :: * -> *).
Stream s m Char =>
[Char] -> ParsecT s u m [Char]
为了使用那个,我需要两个:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE KindSignatures #-}
但是,如果我删除forall
,我可以将类型简化为以下内容,编译器仍然接受它:
string' :: Stream s m Char => [Char] -> ParsecT s u m [Char]
但是,不是很简单。更进一步,放弃了遗体:
string' :: [Char] -> ParsecT s u m [Char]
我明白了:
No instance for (Stream s m Char) arising from a use of ‘char'’
我想:
{-# LANGUAGE NoMonomorphismRestriction #-}
可能会让我离开,但事实并非如此。
这些大多数都是我的头脑,所以我不想盲目地复制粘贴推断的类型签名而不首先获得更多的洞察力。任何人都可以了解这些意味着什么,注释parsec-heavy代码的最佳实践是什么,forall
如果可以在不引起任何编译器错误的情况下省略它,以及是否存在任何别名我可以用来使这些更具可读性的技巧?
答案 0 :(得分:4)
我不是parsec
的专家,所以如果他们想要解释这些类型,我会让其他人做繁重的工作,但这里有一些想法:
通常,包尝试导出更友好的类型同义词。在这种情况下,您可以使用
type Parsec s u = ParsecT s u Identity -- in Text.Parsec.Prim
type Parser = Parsec String () -- in Text.Parsec.String
这样可以获得vimL :: Parser Node
,这应该更有意义 - 它是一个可以在String
上运行以生成Node
的解析器。
forall
在此上下文中你很少,这就是为什么有可用的友好类型同义词,你应该使用它。但是,我愿意打赌,在它自己的胆量parsec
中大量使用更高级别的类型,如果没有forall
则无法表达,这就是为什么GHC建议您拥有的明确的forall
。
(简而言之,forall x. <something-with-x>
与<some-thing-with-x>
相同,但如果您在签名中间有forall
,事情会变得更加糟糕。)
parsec
(from the documentation)上的一些内容。类型ParsecT s u m a
表示可能的最通用解析器。阅读the source中的评论会有所帮助。
s
描述了流类型。抽象意义上的解析器采用一系列符号并将它们转换为某种结构化输出形式。a
是输出表单的类型。u
是用户状态类型。 parsec
已经跟踪了一些状态信息(比如你在文本中的位置,这样它就可以给你一个有意义的解析错误信息)所以让用户打包到他们想要携带的状态是有意义的(在2.12 Advanced: User State)m
是正在运行事物的基础monad。我认为如果你修理单子那么这一部分就会很明显...... 然后,出现了一些特殊情况:
m = Identity
意味着我们不需要monadic执行上下文。 (Parsec s u a
类型的同义词适用于这种情况。)u = ()
表示我们不需要保留任何州信息。s = String
表示我们的输入(流)将是一个字符串。 (结合上面的其他两个选项,即Parser a
类型同义词的用途。)最后,string' :: forall s u (m :: * -> *). Stream s m Char => [Char] -> ParsecT s u m [Char]
表示输出是String = [Char]
,用户状态,monadic上下文和输入可以是任何东西 - 只要它们满足某些条件,因此Stream s m Char
约束
该约束Stream s m t
表示您必须能够将流输入类型s
“展开”为m (Maybe (t,s))
。 m
部分意味着此展开可以在monadic上下文中进行,Maybe
部分处理的事实是,只要您有输入就可以展开,t
是令牌正在取消流的前端,s
是流的其余部分。最后,流s
的类型必须唯一地标识出现的令牌t
的类型,因此存在功能依赖s -> t
。