[GHC] #14961: QuantifiedConstraints: introducing classes through equality constraints fails

GHC ghc-devs at haskell.org
Thu Mar 22 11:58:26 UTC 2018


#14961: QuantifiedConstraints: introducing classes through equality constraints
fails
-------------------------------------+-------------------------------------
        Reporter:  mrkgnao           |                Owner:  (none)
            Type:  bug               |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler          |              Version:  8.5
      Resolution:                    |             Keywords:
                                     |  QuantifiedConstraints wipT2893
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  GHC rejects       |  Unknown/Multiple
  valid program                      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:  #14860            |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by mrkgnao):

 Sorry for the trouble! I truly don't understand what happened when I made
 the report: when I check it now, all the definitions fail, just as you
 said.

 And, worse, I just rewrote the code that I tried to "simplify" into the
 example in the original bug report, and it works now!

 Perhaps it might give you an idea of what I was trying to achieve.

 {{{#!hs
 {-# LANGUAGE ConstraintKinds           #-}
 {-# LANGUAGE InstanceSigs              #-}
 {-# LANGUAGE FlexibleContexts          #-}
 {-# LANGUAGE FlexibleInstances         #-}
 {-# LANGUAGE GADTs                     #-}
 {-# LANGUAGE MultiParamTypeClasses     #-}
 {-# LANGUAGE RankNTypes                #-}
 {-# LANGUAGE TypeFamilies              #-}
 {-# LANGUAGE TypeFamilyDependencies    #-}
 {-# LANGUAGE TypeInType                #-}
 {-# LANGUAGE TypeOperators             #-}
 {-# LANGUAGE UndecidableInstances      #-}
 {-# LANGUAGE QuantifiedConstraints     #-}

 module QC where

 import           Data.Kind

 import           Control.Arrow              (left, right, (&&&), (|||))
 import           Control.Category
 import           Prelude                    hiding (id, (.))

 import           Data.Coerce

 class    (forall x. f x => g x) => f ~=> g
 instance (forall x. f x => g x) => f ~=> g

 type family (#) (p :: Type -> Type -> Type) (ab :: (Type, Type))
   = (r :: Type) | r -> p ab where
   p # '(a, b) = p a b

 newtype Glass
   :: ((Type -> Type -> Type) -> Constraint)
   -> (Type, Type) -> (Type, Type) -> Type where
   Glass :: (forall p. z p => p # ab -> p # st) -> Glass z st ab

 data A_Prism

 type family ConstraintOf (tag :: Type)
   = (r :: (Type -> Type -> Type) -> Constraint) where
   ConstraintOf A_Prism = Choice

 _Left0
   :: Glass Choice
        '(Either a x, Either b x)
        '(a, b)
 _Left0 = Glass left'

 _Left1
   :: c ~=> Choice
   => Glass c '(Either a x, Either b x) '(a, b)
 _Left1 = Glass left'

 -- fails with
 -- • Could not deduce (Choice p)
 -- _Left2
 --   :: (forall p. c p => ConstraintOf A_Prism p)
 --   => Glass c '(Either a x, Either b x) '(a, b)
 -- _Left2 = Glass left'

 _Left3
   :: d ~ ConstraintOf A_Prism
   => (forall p . c p => d p)
   => Glass c
        '(Either a x, Either b x)
        '(a, b)
 _Left3 = Glass left'

 -- fails to typecheck unless at least a partial type signature is provided
 -- l :: c ~=> Choice => Glass c _ _
 -- l = _Left1 . _Left1

 newtype Optic o st ab where
   Optic
     :: (forall c d. (d ~ ConstraintOf o, c ~=> d) => Glass c st ab)
     -> Optic o st ab

 _Left
   :: Optic A_Prism
        '(Either a x, Either b x)
        '(a, b)
 _Left = Optic _Left1

 instance Category (Glass z) where
   id :: Glass z a a
   id = Glass id

   (.) :: Glass z uv ab -> Glass z st uv -> Glass z st ab
   Glass abuv . Glass uvst = Glass (uvst . abuv)

 class Profunctor (p :: Type -> Type -> Type) where
   dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
   lmap :: (a -> b) -> p b c -> p a c
   rmap :: (b -> c) -> p a b -> p a c

 class Profunctor p => Choice (p :: Type -> Type -> Type) where
   left' :: p a b -> p (Either a c) (Either b c)
   right' :: p a b -> p (Either c a) (Either c b)
 }}}

 Iceland_jack suggested an interesting encoding of profunctor optics
 (https://pbs.twimg.com/media/DY1y3voX4AAh1Jj.jpg:large) where, instead of
 specifying the constraint `c` like is usually done in an encoding like

 {{{
 type Optic c s t a b = forall p. c p => p a b -> p s t
 }}}

 we just put a bound on it, to get something like

 {{{
 type QOptic c s t a b = forall p d. d ~=> c => Optic d s t a b
 }}}

 Then we can make optics where the profunctor satisfies _at least_ such-
 and-such constraint, but the quantified constraint lets you take it to
 satisfy something stronger (hence making it less general). This would mean
 that one could define a class whose instances would have to define
 something that was at least as good as a `Prism`, say, but the instances
 were free to define something better like a `Lens` or an `Iso`. Kmett has
 a sketch of this idea:
 https://gist.github.com/ekmett/af1c460582b1de467c8461abdf134b6f.

 I found that interesting, but for the fact that bindings with quantified
 constraints don't seem very friendly to inference, as I expected (e.g.
 `:type` doesn't work without `+v`, and, as shown above for the `l`
 binding, you can't write `_Left1 . _Left1` because the quantified `c` is
 ambiguous).

 So I thought of trying to encode things as newtypes: we could have a type
 tag `o` (like `A_Prism`) above that would, through a type family
 `ConstraintOf`, give us a constraint that we could then use as a minimum
 bound. This makes things much less fragile by wrapping the polymorphism
 inside a constructor, which I found appealing.

 Now, as in ticket:14860, this would mean trying to quantify a constraint
 involving a type family, which is not going to work directly. Hence the
 introduction of the `d` variable, which it seems does get substituted in,
 as one can check with `:type +v`:

 {{{
 *QC> :t +v _Left1
 _Left1
   :: (c ~=> Choice) => Glass c '(Either a x, Either b x) '(a, b)
 }}}

 Now, this is weird, but I could swear that the trick with the `d` type
 variable didn't work when I submitted the bug report (likely a result of
 me reloading the wrong file in GHCi or something silly like that), and I
 "simplified" it down, poorly, to what I gave you.

 Apologies :)

-- 
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/14961#comment:3>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list