Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this incremental parser a functor, if so how would `fmap` be implemented?

I really hate asking this kind of question but I'm at the end of my wits here. I am writing an incremental parser but for some reason, just cannot figure out how to implement functor instance for it. Here's the code dump:

Input Data Type

Input is data type yielded by parser to the coroutine. It contains the current list of input chars being operated on by coroutine and end of line condition

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

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

Output Data Type

Output is data type yielded by coroutine to Parser. It is either a Failed message, Done [b], or Partial ([a] -> Output a b), where [a] is the current buffer passed back to the parser

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

The parser takes [a] and yields a buffer [a] to coroutine, which yields back Output a b

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

Functor Implementation

It seems like all I have to do is fmap the function g onto the coroutine, like follows:

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

But it does not type check:

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
like image 911
xiaolingxiao Avatar asked Jul 18 '13 21:07

xiaolingxiao


1 Answers

As Philip JF declared, it's not possible to have an instance Functor (ParserI a). The proof goes by variance of functors—any (mathematical) functor must, for each of its arguments, be either covariant or contravariant. Normal Haskell Functors are always covariant which is why

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

Haskell Contravariant functors have the similar

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

In your case, the b index in ParserI a b would have to be both covariant and contravariant. The quick way of figuring this out is to relate covariant positions to + and contravariant to - and build from some basic rules.

Covariant positions are function results, contravariant are function inputs. So a type mapping like type Func1 a b c = (a, b) -> c has a ~ -, b ~ -, and c ~ +. If you have functions in output positions, you multiply all of the argument variances by +1. If you have functions in input positions you multiply all the variances by -1. Thus

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

has the same variances as Func1 but

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

has a ~ 1, b ~ -1, and c ~ 1. Using these rules you can pretty quickly see that Output has variances like Output - + and then ParserI uses Output in both negative and positive positions, thus it can't be a straight up Functor.


But there are generalizations like Contravariant. The particular generalization of interest is Profunctor (or Difunctors which you see sometimes) which goes like so

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

the quintessential example of which being (->)

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

i.e. it "extends" the function both after (like a usual Functor) and before. Profunctors f are thus always mathematical functors of arity 2 with variance signature f - +.

So, by generalizing your ParserI slightly, letting there be an extra parameter to split the ouput types in half, we can make it a 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)

and then you can wrap it up

type ParserI a b = ParserIC a b b

and provide a slightly less convenient mapping function over b

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

which really drives home the burden of having the variances go both ways---you need to have bidirectional maps!

like image 141
J. Abrahamson Avatar answered Sep 28 '22 19:09

J. Abrahamson