【问题标题】:Is this incremental parser a functor, if so how would `fmap` be implemented?这个增量解析器是仿函数吗,如果是,“fmap”将如何实现?
【发布时间】:2013-07-18 21:58:23
【问题描述】:

我真的很讨厌问这种问题,但我已经无能为力了。我正在编写一个增量解析器,但由于某种原因,我无法弄清楚如何为其实现仿函数实例。这是代码转储:

输入数据类型

输入是解析器向协程产生的数据类型。它包含协程正在操作的输入字符的当前列表和行尾条件

data Input a = S [a] Bool deriving (Show)

instance Functor Input where
    fmap g (S as x) = S (g <$> as) x

输出数据类型

输出是协程向 Parser 产生的数据类型。它可以是 Failed 消息、Done [b] 或 Partial ([a] -> Output a b),其中 [a] 是传回解析器的当前缓冲区

data Output a b = Fail String | Done [b] | Partial ([a] -> Output a b)

instance Functor (Output a) where
    fmap _ (Fail s)    = Fail s
    fmap g (Done bs)   = Done $ g <$> bs
    fmap g (Partial f) = Partial $ \as -> g <$> f as

解析器

解析器获取 [a] 并产生一个缓冲区 [a] 给协程,协程返回输出 a b

data ParserI a b = PP { runPi :: [a] -> (Input a -> Output a b) -> Output a b }

函子实现

似乎我所要做的就是将函数 g 映射到协程上,如下所示:

instance Functor (ParserI a) where
    fmap g p = PP $ \as k -> runPi p as (\xs -> fmap g $ k xs)

但它没有类型检查:

Couldn't match type `a1' with `b'
  `a1' is a rigid type variable bound by
       the type signature for
         fmap :: (a1 -> b) -> ParserI a a1 -> ParserI a b
       at Tests.hs:723:9
  `b' is a rigid type variable bound by
      the type signature for
        fmap :: (a1 -> b) -> ParserI a a1 -> ParserI a b
      at Tests.hs:723:9
Expected type: ParserI a b
  Actual type: ParserI a a1

【问题讨论】:

  • ParserI 不是函子。不存在实例。
  • Oh X( 我可以请你解释一下为什么吗?我该如何重组它使其成为一个仿函数?例如在 Attoparsec.Incremental (已弃用)中,协程的类型类似于 (c -> 输入 a -> 输出 a b),但我不知道 c 是干什么用的
  • fmap g p = PP $ \as k -&gt; runPi p as (\xs -&gt; fmap g $ k as) as 本来应该是一个xs,不是吗?
  • 对。现在它是有道理的(错误消息)。问题是您需要将Input a -&gt; Output a b 作为第二个参数传递给runPi p。但是你所拥有的kInput a -&gt; Output a c(当g 的类型是b -&gt; c 时)。您需要某种方法将Input a -&gt; Output a c 转换为Input a -&gt; Output a b。但是您无法从c 创建b。类型变量b 出现在错误的位置,使ParserI 成为Functor。这就是 Philip JF 所说的。
  • 您是否确实需要 PP 构造函数的第二个参数作为函数,或者您可以以其他方式存储该信息吗?这显然是问题所在。

标签: parsing haskell coroutine


【解决方案1】:

正如 Philip JF 所说,不可能有 instance Functor (ParserI a)。证明是通过函子的方差进行的——任何(数学)函子对于它的每个参数都必须是协变的或逆变的。普通的 Haskell Functors 总是协变的,这就是为什么

fmap :: (a -> b) -> (f a -> f b)`

Haskell Contravariant functors 有类似的

contramap :: (b -> a) -> (f a -> f b)`

在您的情况下,ParserI a b 中的 b 索引必须是协变和逆变的。解决这个问题的快速方法是将协变位置与 + 相关联并与 - 逆变换相关联,并根据一些基本规则构建。

协变位置是函数结果,逆变是函数输入。所以像type Func1 a b c = (a, b) -&gt; c 这样的类型映射有a ~ -b ~ -c ~ +。如果您在输出位置有 函数,则将所有参数方差乘以 +1。如果您在 input 位置有函数,则将所有方差乘以 -1。因此

type Func2 a b c = a -> (b -> c)

Func1 具有相同的差异,但

type Func3 a b c = (a -> b) -> c

a ~ 1b ~ -1c ~ 1。使用这些规则,您可以很快看到Output 具有类似Output - + 的差异,然后ParserI 在负数和正数位置都使用Output,因此它不可能是直升Functor


但也有像Contravariant 这样的概括。感兴趣的特定概括是Profunctor(或您有时会看到的Difunctors),就像这样

class Profunctor f where
  promap :: (a' -> a) -> (b -> b') -> (f a b -> f a' b')

典型的例子是(-&gt;)

instance Profunctor (->) where
  promap f g orig = g . orig . f

即它在之后(如通常的Functor)和之前“扩展”了该功能。 Profunctors f 因此总是具有方差签名f - + 的arity 2 的数学函子。

因此,通过稍微概括您的ParserI,让有一个额外的参数将输出类型分成两半,我们可以将其设为Profunctor

data ParserIC a b b' = PP { runPi :: [a] -> (Input a -> Output a b) -> Output a b' }

instance Profunctor (ParserIC a) where
  promap before after (PP pi) = 
    PP $ \as k -> fmap after $ pi as (fmap before . k)

然后你可以把它包起来

type ParserI a b = ParserIC a b b

并提供比b稍不方便的映射功能

mapPi :: (c -> b) -> (b -> c) -> ParserI a b -> ParserI a c
mapPi = promap

这真的让你背负着双向变化的负担——你需要有双向地图!

【讨论】:

  • 像往常一样,一旦您将它们记入您的大脑,它们就会开始到处出现。很高兴我的例子有所帮助! :)
  • 所以既然这不是一个函子,如果它也没有办法应用程序吗?是否也无法实现替代和单子实例?还是在产品类别上存在某种等效结构?
  • 您可以仅使用最后一个参数instance Applicative (ParserIC a b) 来创建ApplicativeAlternativeMonad,这可能就足够了,具体取决于您的需要。我不知道是否有使用这两个参数的自然概括。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多