了解parsec类型注释

时间:2016-06-10 02:28:37

标签: haskell parsec

我使用parsec将一些源代码解析为AST。我最近打开了-Wall-W选项,以便抓住"可疑"代码,它抱怨许多the parsec-related top-level functions in this file没有明确的类型扩展。

示例1

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

并且编译器仍然会接受它。但它仍然不是我非常理解的东西。

示例2

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

示例3

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如果可以在不引起任何编译器错误的情况下省略它,以及是否存在任何别名我可以用来使这些更具可读性的技巧?

1 个答案:

答案 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,事情会变得更加糟糕。)

修改

parsecfrom 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