4

I want to write a specialization rewrite rule for a Megaparsec combinator so that the rule only fires when the input type is a ByteString.

{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

import Data.Void
import Text.Megaparsec
import qualified Data.ByteString as B

combin :: forall e s m a. (MonadParsec e s m)
    => m a
    -> m a
combin = label "String generic"

combinByteString :: forall e s m a. (MonadParsec e s m, s ~ B.ByteString)
    => m a
    -> m a
combinByteString = label "ByteString specialized"

main = do
    parseTest (combin empty :: Parsec Void String String) ""
    parseTest (combin empty :: Parsec Void B.ByteString String) ""

{-# NOINLINE combin #-}
{-# NOINLINE combinByteString #-}
{-# RULES "combin/ByteString" combin = combinByteString #-}

When I try to build this it fails:

$ cabal v2-run
Build profile: -w ghc-8.6.5 -O1
Main.hs:25:40: error:
    • Couldn't match type ‘s’ with ‘B.ByteString’
        arising from a functional dependency between constraints:
          ‘MonadParsec e B.ByteString m’
            arising from a use of ‘combinByteString’ at Main.hs:25:40-55
          ‘MonadParsec e s m’
            arising from the RULE "combin/ByteString" at Main.hs:25:11-55
      ‘s’ is a rigid type variable bound by
        the RULE "combin/ByteString"
        at Main.hs:25:11-55
    • In the expression: combinByteString
      When checking the transformation rule "combin/ByteString"
   |
25 | {-# RULES "combin/ByteString" combin = combinByteString #-}

The input stream type parameter s of MonadParsec has a functional dependency on the Monad parameter m.

class (Stream s, MonadPlus m) => MonadParsec e s m | m -> e s where

Here's a specialize.cabal file for trying the build.

cabal-version:       >=1.10
name:                specialize
version:             0.1.0.0
build-type:          Simple

executable specialize
  main-is:             Main.hs
  build-depends:       base >= 4
                      ,megaparsec
                      ,bytestring
  default-language:    Haskell2010

If successful, the output should look like this:

1:1:
  |
1 | <empty line>
  | ^
expecting String generic
1:1:
  |
1 | <empty line>
  | ^
expecting ByteString specialized

Advice?

James Brock
  • 3,236
  • 1
  • 28
  • 33
  • 1
    It looks like the fundep wants `s` to follow from `m`, so you can't have different `s` (`String` or `ByteString`) for the same `m`. Have you tried fixing `m` instead, like `m ~ Parsec e ByteString a`? – Fyodor Soikin Oct 25 '19 at 14:59
  • @fyodor-soykin I have tried fixing `m` in ways similar to what you suggest, and I still can't get it to build, and even if it did build it wouldn't be quite right because I want this to work for all of the instances of `MonadParsec`, not just `ParsecT`. – James Brock Oct 25 '19 at 15:05
  • 1
    Do you have to have a `RULE`? Can you do a type class instead? – Fyodor Soikin Oct 25 '19 at 15:15
  • @FyodorSoikin I'm willing to try any solution, but I don't want to change the public API type signature of `combin` in any way. I don't want to add any special class constraints to the public API. – James Brock Oct 25 '19 at 15:18
  • https://stackoverflow.com/questions/19745038/ghc-rewrite-rule-specialising-a-function-for-a-type-class – James Brock Oct 25 '19 at 15:19
  • 2
    Yes, you can totally use `ifcxt` – Fyodor Soikin Oct 25 '19 at 15:23

1 Answers1

0

This rule works, but only for GHC 8.8.1, not for GHC 8.6.5.

{-# LANGUAGE TypeApplications #-}

{-# RULES "combin/ByteString" forall e. forall. 
    combin @e @B.ByteString = combinByteString @e @B.ByteString
#-}

This rule works with GHC 8.6.5 and 8.0.2

{-# RULES "combin/ByteString"
 forall (pa :: ParsecT e B.ByteString m a).
 combin @e @B.ByteString @(ParsecT e B.ByteString m) @a pa = 
 combinByteString @e @B.ByteString @(ParsecT e B.ByteString m) @a pa
#-}
James Brock
  • 3,236
  • 1
  • 28
  • 33