あどけない話

インターネットに関する技術的な話など

Parsec2と3

GHC 6.10 に付随していた Parsec のバージョンは 2 だ。現在の Parsec のバージョンは 3 であり、以下のような特徴がある。

特に後者は重要だ。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