Parsec2と3
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