Search code examples
haskelltype-inferencegadttype-families

TypeFamilies or GADTs suddenly breaks the valid code


I have very innocent-looking code

data Config = Config
    { cInts    :: [Int]
    , cStrings :: [String] }

instance Semigroup Config where
    c1 <> c2 = Config
        { cInts    = andCombiner cInts
        , cStrings = andCombiner cStrings }
      where
        andCombiner field = field c1 <> field c2

It compiles and works fine. However if I add TypeFamilies or GADTs extension I see very strange error:

.../Main.hs:19:22: error:
    • Couldn't match type ‘Int’ with ‘[Char]’
      Expected type: [String]
        Actual type: [Int]
    • In the ‘cStrings’ field of a record
      In the expression:
        Config {cInts = andCombiner cInts, cStrings = andCombiner cStrings}
      In an equation for ‘<>’:
          c1 <> c2
            = Config
                {cInts = andCombiner cInts, cStrings = andCombiner cStrings}
            where
                andCombiner field = field c1 <> field c2
   |
19 |         , cStrings = andCombiner cStrings }
   |                      ^^^^^^^^^^^^^^^^^^^^

.../Main.hs:19:34: error:
    • Couldn't match type ‘[Char]’ with ‘Int’
      Expected type: Config -> [Int]
        Actual type: Config -> [String]
    • In the first argument of ‘andCombiner’, namely ‘cStrings’
      In the ‘cStrings’ field of a record
      In the expression:
        Config {cInts = andCombiner cInts, cStrings = andCombiner cStrings}
   |
19 |         , cStrings = andCombiner cStrings }
   |                                  ^^^^^^^^

What can be the reason for this compiler error?


Solution

  • This is due to -XMonoLocalBinds which -XGADTs and -XTypeFamilies imply. You can get your code to compile again by adding a type signature to andCombiner (or by turning on -XNoMonoLocalBinds, although I do not recommend that):

    instance Semigroup Config where
        c1 <> c2 = Config
            { cInts    = andCombiner cInts
            , cStrings = andCombiner cStrings }
          where
            andCombiner :: Semigroup a => (Config -> a) -> a
            andCombiner field = field c1 <> field c2
    

    Using the terminology from the GHC docs I've linked, andCombiner is not fully generalized because it mentions c1 and c2 which are not closed or imported.