GHC 6.10 に付随していた Parsec のバージョンは 2 だ。現在の Parsec のバージョンは 3 であり、以下のような特徴がある。
- モナド変換子として実装されているので、下回りを自由に変更できる。
- Applicative と Alternative のインスタンスになっている。
特に後者は重要だ。Applicative スタイルで書かないパーサーは、パーサーじゃないって思えるほどに。
僕が公開しているライブラリー群は、Haskell Platform で Parsec 3 が提供されるだろうという予想の元に、Parsec 3 を使っていた。しかし、Haskell Platform で提供されたのは Parsec 2 だ。
結局、Parsec 2 と Parsec 3 の違いを吸収するモジュールが必要になった。Real World Haskell に載っている ApplicativeParsec でいいかとも思ったが、これを複数のライブラリに入れて、GHC 6.12 でリンクすると overlapping instances というエラーを起こす。
隠されたモジュールだし、GHC 6.10 ではこのエラーは起きないので、GHC 6.12 のバグだろう。現実世界は完璧ではない。結局、Parsec を Applicative と Alternative のインスタンスにすることはあきらめて、Control.Applicative を import せずに (<$>) や (<*>) を定義することにした。
Parsec を使うモジュールでは、それ以外のモナドは使わないから、Control.Applicative を import しなくてもいい。ByteString への対応は、以下のコードを改造して、Parsec 3 のときは ByteString を使い、Parsec 2 のときは ByteString を String へ unpack で変換するコードを書けばよい。
{-# LANGUAGE CPP #-}
#ifndef MIN_VERSION_parsec
#define MIN_VERSION_parsec(x,y,z) 0
#endif
#if MIN_VERSION_parsec(3,0,0)
module Parsec (
module Control.Applicative
, module Text.Parsec
, module Text.Parsec.String
) where
import Control.Applicative hiding (many,optional,(<|>))
import Text.Parsec
import Text.Parsec.String
#else
module Parsec (
module Text.ParserCombinators.Parsec
, (<$>), (<$), (<*>), (<*), (*>), pure
) where
import Control.Monad (ap, liftM)
import Text.ParserCombinators.Parsec
{-
GenParser cannot be an instance of Applicative and Alternative
due to the overlapping instances error, sigh!
-}
(<$>) :: Monad m => (a -> b) -> m a -> m b
(<$>) = liftM
(<$) :: Monad m => a -> m b -> m a
a <$ m = m >> return a
(<*>) :: Monad m => m (a -> b) -> m a -> m b
(<*>) = ap
(*>) :: Monad m => m a -> m b -> m b
(*>) = (>>)
(<*) :: Monad m => m a -> m b -> m a
m1 <* m2 = do x <- m1
m2
return x
pure :: Monad m => a -> m a
pure = return
infixl 4 <$>, <$, <*>, <*, *>
#endif