Say I have a monad that emits soft failure through Writer. A simplified version goes like this (for the record, I'm using GHC 9.0.2):
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
-- {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Lib where
import Control.Monad.Writer.CPS
import Data.Maybe
verify :: MonadWriter [String] m => some -> args -> m ()
verify _ _ = fix \(_ :: m ()) ->
do
let warn = tell . (: [])
a = Just 1 :: Maybe Int
b = Just [1, 23] :: Maybe [Int]
c = Just (id :: forall a. a -> a)
-- isJust' :: String -> Maybe a -> m ()
isJust' tag v = unless (isJust v) do
warn $ tag <> " is Nothing"
isJust' "a" a
isJust' "b" b
isJust' "c" c
All goes fine until I add GADTs extension, then GHC fails to find the most general type for isJust':
• Couldn't match type ‘[Int]’ with ‘Int’
Expected: Maybe Int
Actual: Maybe [Int]
• In the second argument of ‘isJust'’, namely ‘b’
In a stmt of a 'do' block: isJust' "b" b
In the expression:
do let warn = tell . (: [])
a = ...
....
isJust' "a" a
isJust' "b" b
isJust' "c" c
|
22 | isJust' "b" b
| ^
/var/tmp/demo/src/Lib.hs:23:17: error:
• Couldn't match type ‘a0 -> a0’ with ‘Int’
Expected: Maybe Int
Actual: Maybe (a0 -> a0)
• In the second argument of ‘isJust'’, namely ‘c’
In a stmt of a 'do' block: isJust' "c" c
In the expression:
do let warn = tell . (: [])
a = ...
....
isJust' "a" a
isJust' "b" b
isJust' "c" c
|
23 | isJust' "c" c
| ^
At which point I have to uncomment type annotation for isJust' to get this to type check - I'm wondering what is at play here, I don't think I'm using any GADTs features?
Side note 1: I usually just slam a NoMonomorphismRestriction to see if it helps, it didn't.
Side note 2: That use of fix is just for getting a hold on that m in the presence of ScopedTypeVariables - a side question is whether there are better ways to do this without relying on verify having an explicit type signature (say this function is defined inside an instance).
The problem is not
GADTsbutMonoLocalBinds, which is automatically enabled byGADTs. The issue is thatisJust' :: String -> Maybe a -> m ()has a polymorphic type signature, meaning you get to choose whatais every time you call it. At least, it would be polymorphic ifMonoLocalBindswasn't enabled. Per the documentation:If a variable does not satisfy one of the three bullets, its type is not generalized, i.e. it does not get to be polymorphic.
There is a rigorous definition of what a "closed variable" is, but the important part is:
The second argument of
isJust'has typeMaybe afor some unknowna, meaning it is not closed (edit: see Li-yao Xia's correction in the comments). So, bullet 1 is not satisfied becauseisJust'is defined withinverify, bullet 2 is not satisfied because the second argument ofisJust'is not closed, and bullet 3 is not satisfied because there are no holes in the type ofisJust'. Thus,isJust'is not polymorphic after all.All this means is that the
ainString -> Maybe a -> m ()is actually a fixed type that GHC has to infer. It sees you callisJust' "a" (a::Maybe Int)and assumesa ~ Int, but then it seesisJust' "b" (b::Maybe [Int])and assumesa ~ [Int]as well. Obviously this is a problem, so GHC complains. The only thing adding the type signature does is tell GHC to makeisJust'polymorphic.Note that you can have
GADTsandNoMonoLocalBindsat the same time, but because type inference with GADTs is tricky this will probably just cause more problems when you actually use GADTs.