这个增量解析器是否是一个算符,如果是这样,`fmap`将如何实现?

时间:2013-07-18 21:58:24

标签: parsing haskell coroutine

我真的很讨厌问这个问题,但我在这里结束了我的智慧。我正在编写增量解析器,但由于某种原因,只是无法弄清楚如何为它实现functor实例。这是代码转储:

输入数据类型

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

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] - &gt;输出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

The Parser

解析器接受[a]并为协程生成缓冲区[a],从而产生返回输出a b

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

Functor Implementation

似乎我所要做的就是将函数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

1 个答案:

答案 0 :(得分:11)

正如Philip JF所宣称的那样,instance Functor (ParserI a)是不可能的。证明了仿函数的变化 - 任何(数学)仿函数对于它的每个参数都必须是协变的或逆变的。普通的Haskell Functor总是协变的,这就是

的原因
fmap :: (a -> b) -> (f a -> f b)`

Haskell Contravariant functors具有类似的

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

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

协变位置是函数结果,逆变是函数输入。因此,type Func1 a b c = (a, b) -> c等类型映射具有a ~ -b ~ -c ~ +。如果输出位置中有函数,则将所有参数方差乘以+1。如果您在输入位置有函数,则将所有差异乘以-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(或者你有时看到的Difunctor),这样就是这样的

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

其中典型的例子是(->)

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

即。它“延伸”了之后的函数(像通常的Functor)和之前一样。因此,Profunctor s 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

这确实使得差异变得双重 - 你需要双向地图!