Skip to content

GHC 9.0 regression: plots-0.1.1.2 fails to build due to ambiguous type variable

Originally observed in a head.hackage CI build here.

The plots-0.1.1.2 library fails to compile using commit 0789f7a1 of the ghc-9.0 branch. Here is a self-contained example with no external dependencies:

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Bug where

import Control.Monad.Reader
import Data.Functor.Const
import Data.Functor.Identity
import Data.Monoid (Any(..))
import Data.Typeable

type TypeableFloat n = (Typeable n, RealFloat n)

type Lens' s a = Lens s s a a
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
type LensLike' f s a = LensLike f s s a a
type LensLike f s t a b = (a -> f b) -> s -> f t
type Getting r s a = (a -> Const r a) -> s -> Const r s
type ASetter s t a b = (a -> Identity b) -> s -> Identity t

data V2 a = V2 !a !a

flipX_Y :: Num n => V2 n -> V2 n
flipX_Y (V2 x y) = V2 (-y) (-x)

class R1 t where
  _x :: Lens' (t a) a

class R1 t => R2 t where
  _y :: Lens' (t a) a

instance R1 V2 where
  _x f (V2 a b) = (`V2` b) <$> f a

instance R2 V2 where
  _y f (V2 a b) = V2 a <$> f b

infixl 8 #
(#) :: a -> (a -> b) -> b
(#) = flip ($)

infixr 4 %~
(%~) :: ASetter s t a b -> (a -> b) -> s -> t
(%~) = over

(^.) :: s -> Getting a s a -> a
s ^. l = getConst (l Const s)

over :: ASetter s t a b -> (a -> b) -> s -> t
over l f = runIdentity . l (Identity . f)

view :: MonadReader s m => Getting a s a -> m a
view l = asks (getConst . l Const)

lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
lens sa sbt afb s = sbt s <$> afb (sa s)

type family V a :: * -> *
type family N a :: *
type Vn a = V a (N a)

data Style (v :: * -> *) n = Style

class HasStyle a where
  applyStyle :: Style (V a) (N a) -> a -> a

type TextFunction b v n = TextAlignment n -> String -> QDiagram b v n Any

data TickLabels b (v :: * -> *) n = TickLabels
  { tlFun     :: [n] -> (n,n) -> [(n, String)]
  , tlTextFun :: TextFunction b v n
  , tlStyle   :: Style v n
  , tlGap     :: n
  }

type instance V (TickLabels b v n) = v
type instance N (TickLabels b v n) = n

class HasTickLabels f a b | a -> b where
  tickLabel :: LensLike' f a (TickLabels b (V a) (N a))

  tickLabelTextFunction :: Functor f => LensLike' f a (TextFunction b (V a) (N a))
  tickLabelTextFunction = tickLabel . lens tlTextFun (\tl f -> tl {tlTextFun = f})

  tickLabelFunction :: Functor f => LensLike' f a ([N a] -> (N a, N a) -> [(N a, String)])
  tickLabelFunction = tickLabel . lens tlFun (\tl f -> tl {tlFun = f})

  tickLabelStyle :: Functor f => LensLike' f a (Style (V a) (N a))
  tickLabelStyle = tickLabel . lens tlStyle (\tl sty -> tl {tlStyle = sty})

  tickLabelGap :: Functor f => LensLike' f a (N a)
  tickLabelGap = tickLabel . lens tlGap (\tl n -> tl {tlGap = n})

instance HasTickLabels f (TickLabels b v n) b where
  tickLabel = id

data ColourBar b n = ColourBar
  { cbTickLabels :: TickLabels b V2 n
  , cbTicks      :: MajorTicks V2 n
  , cbWidth      :: n
  }

type instance V (ColourBar b n) = V2
type instance N (ColourBar b n) = n

data MajorTicks (v :: * -> *) n = MajorTicks
  { matFunction :: (n,n) -> [n]
  }

type instance V (MajorTicks v n) = v
type instance N (MajorTicks v n) = n

class HasMajorTicks f a where
  majorTicks :: LensLike' f a (MajorTicks (V a) (N a))

  majorTicksFunction :: Functor f => LensLike' f a ((N a, N a) -> [N a])
  majorTicksFunction = majorTicks . lens matFunction (\mat a -> mat {matFunction = a})

instance HasMajorTicks f (MajorTicks v n) where
  majorTicks = id

data Path (v :: * -> *) n = Path

data (:-:) u v = (u -> v) :-: (v -> u)
infixr 7 :-:

instance Semigroup (a :-: a) where
  (f :-: f') <> (g :-: g') = f . g :-: g' . f'

instance Monoid (v :-: v) where
  mempty  = id :-: id

data Transformation v n = Transformation (v n :-: v n) (v n :-: v n) (v n)

class Transformable t where
  transform :: Transformation (V t) (N t) -> t -> t

translation :: v n -> Transformation v n
translation = Transformation mempty mempty

translate :: (Transformable t) => Vn t -> t -> t
translate = transform . translation

class Transformable t => Renderable t b where

orient :: HasOrientation o => o -> a -> a -> a
orient o h v =
  case view orientation o of
    Horizontal -> h
    Vertical   -> v

data Orientation = Horizontal | Vertical

class HasOrientation a where
  orientation :: Lens' a Orientation
instance HasPlacement (ColourBar b n) where
  placement = undefined
instance HasOrientation (ColourBar b n) where
  orientation = undefined
instance Functor f => HasMajorTicks f (ColourBar b n) where
  majorTicks = lens cbTicks (\c a -> c {cbTicks = a})

data ColourMap = ColourMap

data QDiagram b (v :: * -> *) n m = QD

instance Semigroup m -- (Metric v, OrderedField n, Semigroup m)
      => HasStyle (QDiagram b v n m) where
  applyStyle = undefined

instance Semigroup m -- (Metric v, OrderedField n, Semigroup m)
  => Monoid (QDiagram b v n m) where
  mempty = undefined

instance Semigroup m -- (Metric v, OrderedField n, Semigroup m)
  => Semigroup (QDiagram b v n m) where
  (<>) = undefined

instance Semigroup m -- (OrderedField n, Metric v, Semigroup m)
      => Transformable (QDiagram b v n m) where
  transform = undefined

type instance V (QDiagram b v n m) = v
type instance N (QDiagram b v n m) = n

data TextAlignment n = BaselineText | BoxAlignedText n n

data Placement = Placement
  { pAt     :: V2 Rational
  }

class HasPlacement a where
  placement :: Lens' a Placement

  placementAt :: Lens' a (V2 Rational)
  placementAt = placement . lens pAt (\p a -> p {pAt = a})

renderColourBar
  :: forall n b. (TypeableFloat n, Renderable (Path V2 n) b)
  => ColourBar b n
  -> ColourMap
  -> (n,n)
  -> n
  -> QDiagram b V2 n Any
renderColourBar cb@ColourBar {..} _cm bnds@(lb,ub) l
  = tLbs

  where
  o, xy :: a -> a -> a
  o      = orient cb
  xy a b = if let V2 x y = cb^.placementAt in x > y
             then a else b

  w   = cbWidth
  f x = (x - (ub + lb)/2) / (ub - lb) * l
  inRange x = x >= lb && x <= ub

  tickXs  = view majorTicksFunction cbTicks bnds
  tickXs' :: [n]
  tickXs' = filter inRange tickXs

  tickLabelXs :: [(n, String)]
  tickLabelXs = view tickLabelFunction cbTickLabels tickXs' bnds
  tLbs :: QDiagram b V2 n Any
  tLbs = foldMap drawTickLabel tickLabelXs
  drawTickLabel :: (n, String) -> QDiagram b (V (TickLabels b V2 n)) (N (TickLabels b V2 n)) Any
  drawTickLabel (x,label) =
    view tickLabelTextFunction cbTickLabels tAlign label
      # translate v
      # applyStyle (cbTickLabels ^. tickLabelStyle)
        where v = V2 (f x) (- w/2 - view tickLabelGap cbTickLabels)
                    # xy id (_y %~ negate)
                    # o id ((_y %~ negate) . flipX_Y)

  tAlign = o (xy (BoxAlignedText 0.5 1) (BoxAlignedText 0.5 0))
             (xy (BoxAlignedText 0 0.5) (BoxAlignedText 1 0.5))

This compiles with GHC 8.10.3 and earlier, but fails to compile with ghc-9.0:

$ ~/Software/ghc-9.0.0.20210130/bin/ghc Bug.hs
[1 of 1] Compiling Bug              ( Bug.hs, Bug.o )

Bug.hs:229:22: error:
    • Could not deduce (HasTickLabels
                          (Const ([n] -> (n, n) -> [(n, String)])) (TickLabels b V2 n) b0)
        arising from a use of ‘tickLabelFunction’
      from the context: (TypeableFloat n, Renderable (Path V2 n) b)
        bound by the type signature for:
                   renderColourBar :: forall n b.
                                      (TypeableFloat n, Renderable (Path V2 n) b) =>
                                      ColourBar b n
                                      -> ColourMap -> (n, n) -> n -> QDiagram b V2 n Any
        at Bug.hs:(204,1)-(210,24)
      The type variable ‘b0’ is ambiguous
      Relevant bindings include
        tickLabelXs :: [(n, String)] (bound at Bug.hs:229:3)
        tickXs' :: [n] (bound at Bug.hs:226:3)
        inRange :: n -> Bool (bound at Bug.hs:222:3)
        tickXs :: [n] (bound at Bug.hs:224:3)
        l :: n (bound at Bug.hs:211:52)
        ub :: n (bound at Bug.hs:211:48)
        cbTickLabels :: TickLabels b V2 n (bound at Bug.hs:211:31)
        (Some bindings suppressed; use -fmax-relevant-binds=N or -fno-max-relevant-binds)
      These potential instance exist:
        instance HasTickLabels f (TickLabels b v n) b
          -- Defined at Bug.hs:100:10
    • In the first argument of ‘view’, namely ‘tickLabelFunction’
      In the expression: view tickLabelFunction cbTickLabels tickXs' bnds
      In an equation for ‘tickLabelXs’:
          tickLabelXs = view tickLabelFunction cbTickLabels tickXs' bnds
    |
229 |   tickLabelXs = view tickLabelFunction cbTickLabels tickXs' bnds
    |                      ^^^^^^^^^^^^^^^^^

Moreover, this compiles with GHC 9.0.1-rc1. This regression was introduced at some point between the following commits:

  • 9183f5a5 (gitlab-ci: Don't run LLVM tests on Debian 9). This commit has an artifact associated with it, and I can confirm that the bug is not present here.
  • 0789f7a1 (Hadrian: show default ghc-bignum backend (fix #18912))
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information