[GHC] #15649: Errors about ambiguous untouchable variable when using constraint variable in RankN type

#15649: Errors about ambiguous untouchable variable when using constraint variable in RankN type -------------------------------------+------------------------------------- Reporter: infinity0 | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.4.3 Keywords: | Operating System: Unknown/Multiple Architecture: | Type of failure: None/Unknown Unknown/Multiple | Test Case: | Blocked By: Blocking: | Related Tickets: Differential Rev(s): | Wiki Page: -------------------------------------+------------------------------------- The following code compiles and works: {{{#!haskell {-# LANGUAGE ConstraintKinds , FlexibleInstances , MultiParamTypeClasses , GADTs , RankNTypes , ScopedTypeVariables , UndecidableSuperClasses #-} import GHC.Types (Constraint) main :: IO () main = do return () class (r a) => DynPS r a where data PSAny r = forall a. DynPS r a => PSAny a class Unconstrained a instance Unconstrained a instance DynPS Unconstrained () newtype DynLoad' r = DynLoad' { unDynLoad' :: forall ref. r ref => ref -> PSAny r } loadAll :: forall a r . (DynPS r a) => DynLoad' r -> a -> Maybe a loadAll loader r = undefined testCallable :: IO (Maybe ()) testCallable = return $ loadAll (undefined :: DynLoad' Unconstrained) () }}} However it's ugly having to expose `DynLoad'` in the API. Ideally we'd like to have: {{{#!haskell loadAll2 :: forall a r . (DynPS r a) => (forall ref. r ref => ref -> PSAny r) -> a -> Maybe a loadAll2 loader r = loadAll (DynLoad' loader :: DynLoad' r) r }}} But this fails with: {{{ Test3.hs:37:6: error: • Couldn't match type ‘r0’ with ‘r’ ‘r0’ is untouchable inside the constraints: r0 ref bound by the type signature for: loadAll2 :: forall ref. r0 ref => ref -> PSAny r0 at Test3.hs:(37,6)-(39,17) ‘r’ is a rigid type variable bound by the type signature for: loadAll2 :: forall a (r :: * -> Constraint). DynPS r a => (forall ref. r ref => ref -> PSAny r) -> a -> Maybe a at Test3.hs:(37,6)-(39,17) Expected type: ref -> PSAny r0 Actual type: ref -> PSAny r • In the ambiguity check for ‘loadAll2’ To defer the ambiguity check to use sites, enable AllowAmbiguousTypes In the type signature: loadAll2 :: forall a r. (DynPS r a) => (forall ref. r ref => ref -> PSAny r) -> a -> Maybe a | 37 | :: forall a r . (DynPS r a) | ^^^^^^^^^^^^^^^^^^^^^^^^... }}} It compiles if we enable `AllowAmbiguousTypes`, but are then forced to use `TypeApplications` as well to actually call it. :( {{{#!haskell -- as above, but add: {-# LANGUAGE AllowAmbiguousTypes, TypeApplications #-} -- and then: testCallable2 :: IO (Maybe ()) --testCallable2 = return $ loadAll2 (undefined :: forall ref. Unconstrained ref => ref -> PSAny Unconstrained) () -- ^ doesn't work either testCallable2 = return $ loadAll2 @() @Unconstrained undefined () }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15649 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15649: Errors about ambiguous untouchable variable when using constraint variable in RankN type -------------------------------------+------------------------------------- Reporter: infinity0 | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.4.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by infinity0): Note that the error also goes away if we specialise `r` to be of kind `*` rather than `* -> Constraint`, for example: {{{#!haskell -- as the first snippet, but add: {-# LANGUAGE FlexibleContexts #-} -- and then: instance DynPS ((~) ()) () loadAll3 :: forall a r . (DynPS ((~) r) a) => (forall ref . r ~ ref => ref -> PSAny ((~) r)) -> a -> Maybe a loadAll3 loader r = loadAll (DynLoad' loader :: DynLoad' ((~) r)) r testCallable3 :: IO (Maybe ()) testCallable3 = return $ loadAll3 undefined () }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15649#comment:1 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15649: Errors about ambiguous untouchable variable when using constraint variable in RankN type -------------------------------------+------------------------------------- Reporter: infinity0 | Owner: (none) Type: bug | Status: closed Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.4.3 Resolution: duplicate | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: #10651, #14921 | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by RyanGlScott): * status: new => closed * resolution: => duplicate * related: => #10651, #14921 Comment: This is expected behavior. As simonpj explains in https://ghc.haskell.org/trac/ghc/ticket/10651#comment:2, GHC does not (in general) unify underneath constraints that might turn into equalities. In your program, you have {{{#!hs (forall ref. r ref => ref -> PSAny r) }}} In order to prove that `(forall ref. r ref => ref -> PSAny r)` equals `(forall ref. r0 ref => ref -> PSAny r0)` (where `r0` is a unification variable), it must conclude that `r ~ r0`. But this is not sound in general, since `r ref` might later unify with, say, `r ~ Int`, which would make `r0 := Int` a valid substitution. Moreover, `r` is not uniquely determined anywhere else in the type signature (unlike in `loadAll`, where it is determined by the `DynLoad r` argument). Therefore, `r0` is marked as untouchable in this type signature. (This is all explained in Section 5 in the [https://www.microsoft.com/en-us/research/wp- content/uploads/2016/02/jfp-outsidein.pdf OutsideIn paper].) See also #10651 and #14921. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15649#comment:2 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15649: Errors about ambiguous untouchable variable when using constraint variable in RankN type -------------------------------------+------------------------------------- Reporter: infinity0 | Owner: (none) Type: bug | Status: closed Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.4.3 Resolution: duplicate | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: #10651, #14921 | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by infinity0): OK, thanks for explaining. Looks like adding a `Proxy r` as mentioned on #10651 also works here, and you don't need `AllowAmbiguousTypes` or `TypeApplications`. It might be good to have the error message (r0 is untouchable etc) mention this workaround. (I had known of the trick of giving a ghost parameter in other similar cases with ambiguous types, but here `r` is of kind `* -> Constraint` so that doesn't work and one *has* to use `Proxy` here.) {{{#!haskell loadAll2 :: forall a r . (DynPS r a) => (forall ref. r ref => ref -> PSAny r) -> Proxy r -> a -> Maybe a loadAll2 loader _ r = loadAll (DynLoad' loader :: DynLoad' r) r testCallable2 :: IO (Maybe ()) testCallable2 = return $ loadAll2 undefined (Proxy :: Proxy Unconstrained) () }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15649#comment:3 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler
participants (1)
-
GHC