I have a little DSL I use from ghci and I finally tracked down a minor annoyance, which is when I comment out some lines in a definition, sometimes certain now-unused `where` definitions now become ambiguity errors. Here's a minimized repro: ``` {-# LANGUAGE OverloadedStrings #-} -- {-# LANGUAGE NoMonomorphismRestriction #-} import Data.String (IsString(..)) data Thing a = Thing -- 1. IsString instance on concrete type. instance IsString (Thing ()) where fromString _ = Thing -- 2. trans is polymorphic trans :: Thing a -> Thing a trans = id -- 3. ambig is a simple binding under the Monomorphism Restriction, and is -- unused. ex :: () ex = () where unused = trans "k" -- Ambiguous type variable 'a0' ``` In place of `ex` imagine a large expression with many things in `where`, and part of the expression was temporarily commented out which just happened to contain the uses of `unused`. As the comment implies, this is ultimately a monomorphism restriction thing. My understanding is that `trans` keeps defaulting from happening and the restriction then forbids leaving `unused` polymorphic. It was confusing to me given that unused is unused, but I'm guessing the type checking runs before dead code elimination. So I think this is all working as intended, or rather, an unexpected interaction between defaulting being a "best effort" heuristic, and monomorphism restriction doing its thing, and the type checking -> dead code order, but all 3 things exist for good reasons which are unlikely to change. The way around is to turn on NoMonomorphismRestriction, which unfortunately counts as a flag change and hence causes ghci to not be able to load .o files... or compile the .o files with that too, but that of course may lead to unexpected redundant evaluation. https://www.haskell.org/onlinereport/decls.html#sect4.5.5 talks about "no way to enforce monomorphic use of an exported binding", but is this still relevant when it's usual to require type signatures for all top-level bindings? Are there still non-toplevel let/where-bound things which could result in redundant work?
I should have tried this before sending! It turns out NoMonomorphismRestriction is not really a usable workaround. First it seems to trigger a bug: Derive/JScore/T.hs:76:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ GHC Bug #20076 https://gitlab.haskell.org/ghc/ghc/-/issues/20076 Assuming you have a partial type signature, you can avoid this error by either adding an extra-constraints wildcard (like `(..., _) => ...`, with the underscore at the end of the constraint), or by avoiding the use of a simplifiable constraint in your partial type signature. prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Enum’ instance Bounded Instrument -- Defined at Derive/JScore/T.hs:132:30 ...plus 23 others ...plus four instances involving out-of-scope types (use -fprint-potential-instances to see them all) This is in a context where all the types are fully specified, and no partial type signatures: ``` data PitchClass = P1 | P2 | P3 | P4 | P5 | P6 | P7 deriving (Eq, Ord, Show, Bounded, Enum) data Pitch = Pitch { pitch_octave :: Int, pitch_pc :: PitchClass } deriving (Eq, Ord, Show) toEnumBounded :: forall a. (Bounded a, Enum a) => Int -> (Int, a) toEnumBounded n = (i, toEnum r) where (i, r) = n `divMod` (fromEnum (maxBound :: a) + 1) add_pc_abs :: Int -> Pitch -> Pitch add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps ``` `toEnumBounded` is considered ambiguous even though `pc2` should be clear. `pc2 :: PitchClass` works around. But more seriously, practically every numeric literal in tests is now ambiguous, e.g. ``` equalf :: (HasCallStack, Show a, ApproxEq.ApproxEq a) => Double -> a -> a -> Test ... test_thing :: Test test_thing = do let trip = id -- to_sec . f rate equalf 0.1 (trip 601) 601 ``` Now equalf can't find Show for equalf or Num for 601. So I guess the restriction is also causing a lot of defaulting, without which life gets very tedious! So it's not just about avoiding some efficiency corner cases, but also helping a lot in ergonomics.
On Sun, Jun 21, 2026 at 01:05:58PM -0700, Evan Laforge wrote:
I should have tried this before sending! It turns out NoMonomorphismRestriction is not really a usable workaround. First it seems to trigger a bug:
Derive/JScore/T.hs:76:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ GHC Bug #20076 https://gitlab.haskell.org/ghc/ghc/-/issues/20076 Assuming you have a partial type signature, you can avoid this error by either adding an extra-constraints wildcard (like `(..., _) => ...`, with the underscore at the end of the constraint), or by avoiding the use of a simplifiable constraint in your partial type signature. prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Enum’ instance Bounded Instrument -- Defined at Derive/JScore/T.hs:132:30 ...plus 23 others ...plus four instances involving out-of-scope types (use -fprint-potential-instances to see them all)
This is in a context where all the types are fully specified, and no partial type signatures:
``` data PitchClass = P1 | P2 | P3 | P4 | P5 | P6 | P7 deriving (Eq, Ord, Show, Bounded, Enum) data Pitch = Pitch { pitch_octave :: Int, pitch_pc :: PitchClass } deriving (Eq, Ord, Show)
toEnumBounded :: forall a. (Bounded a, Enum a) => Int -> (Int, a) toEnumBounded n = (i, toEnum r) where (i, r) = n `divMod` (fromEnum (maxBound :: a) + 1)
add_pc_abs :: Int -> Pitch -> Pitch add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps ```
`toEnumBounded` is considered ambiguous even though `pc2` should be clear. `pc2 :: PitchClass` works around.
With just the quoted above code fragment and "NoMonomorphismRestriction", testing a range of compiler versions I only see the reported "GHC Bug #20076" with GHC 9.4 and 9.6, but the code compiles successfully only with a now somewhat dated GHC 10.0 snapshot I built back in April. Are you using any additional relevant compiler options? -- Viktor. 🇺🇦 Слава Україні! GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help [1 of 1] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: • Ambiguous type variable ‘b0’ prevents the constraint ‘(Bounded b0)’ from being solved. • When checking that the inferred type oct :: forall b. (Bounded b, Enum b) => Int is as general as its inferred signature oct :: Int In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, no modules loaded. ---- GHCi, version 9.2.8: https://www.haskell.org/ghc/ :? for help [1 of 1] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: • Ambiguous type variable ‘a0’ arising from GHC Bug #20076 prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. These potential instances exist: instance Bounded Ordering -- Defined in ‘GHC.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 instance Bounded () -- Defined in ‘GHC.Enum’ ...plus 19 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • When checking that the inferred type oct :: forall {a}. (Bounded a, Enum a) => Int is as general as its inferred signature oct :: Int In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, no modules loaded. ---- GHCi, version 9.4.8: https://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: • Ambiguous type variable ‘a0’ GHC Bug #20076 https://gitlab.haskell.org/ghc/ghc/-/issues/20076 Assuming you have a partial type signature, you can avoid this error by either adding an extra-constraints wildcard (like `(..., _) => ...`, with the underscore at the end of the constraint), or by avoiding the use of a simplifiable constraint in your partial type signature. prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 ...plus 20 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, no modules loaded. ---- GHCi, version 9.6.4: https://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ GHC Bug #20076 https://gitlab.haskell.org/ghc/ghc/-/issues/20076 Assuming you have a partial type signature, you can avoid this error by either adding an extra-constraints wildcard (like `(..., _) => ...`, with the underscore at the end of the constraint), or by avoiding the use of a simplifiable constraint in your partial type signature. prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 ...plus 20 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, no modules loaded. ---- GHCi, version 9.8.3: https://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ arising when matching required constraints in a group involving ‘oct’ prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 ...plus 20 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, no modules loaded. ---- GHCi, version 9.10.3: https://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ arising when matching required constraints in a group involving ‘oct’ prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Internal.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 ...plus 20 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, unloaded all modules. ---- GHCi, version 9.12.4: https://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted ) /tmp/foo.hs:15:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ arising when matching required constraints in a group involving ‘oct’ prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Internal.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 ...plus 20 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, unloaded all modules. ---- GHCi, version 9.14.1: https://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted )[main] /tmp/foo.hs:15:11: error: [GHC-39999] • Ambiguous type variable ‘a0’ arising from matching required constraints in a binding group involving ‘oct’ prevents the constraint ‘(Bounded a0)’ from being solved. Probable fix: use a type annotation to specify what ‘a0’ should be. Potentially matching instances: instance Bounded Ordering -- Defined in ‘GHC.Internal.Enum’ instance Bounded PitchClass -- Defined at /tmp/foo.hs:5:30 ...plus 20 others ...plus three instances involving out-of-scope types (use -fprint-potential-instances to see them all) • In an equation for ‘add_pc_abs’: add_pc_abs steps (Pitch octave pc) = Pitch (octave + oct) pc2 where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | 15 | where (oct, pc2) = toEnumBounded $ fromEnum pc + steps | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Failed, unloaded all modules. ---- GHCi, version 10.0.0.20260427: https://www.haskell.org/ghc/ :? for help Using default language edition: GHC2024 [1 of 2] Compiling Main ( /tmp/foo.hs, interpreted )[main] Ok, one module loaded.
With just the quoted above code fragment and "NoMonomorphismRestriction", testing a range of compiler versions I only see the reported "GHC Bug #20076" with GHC 9.4 and 9.6, but the code compiles successfully only with a now somewhat dated GHC 10.0 snapshot I built back in April.
I was in fact on 9.6, and in further fact had only recently upgraded to that! However, while trying to track down a module loading bug, I upgraded again to 9.12.4, and I get the same "Ambiguous type variable" you got above. Is it actually ambiguous? To the bare eye it still looks to me like it should be pretty clearly forced to be PitchClass by the Pitch call. fromEnum is forced in the same way. In any case, it's easily worked around with a type annotation. All of the literals becoming ambiguous is a bigger problem! I wound up just pasting a bunch of {-# LANUAGE NoMonomorphismRestriction #-} only on the specific DSL-using modules, that's good enough and better targeted, perhaps it was the right solution all along. Now I'm just curious about why ghc can't figure out the types.
participants (2)
-
Evan Laforge -
Viktor Dukhovni