Ticket #1482 (closed bug: invalid)

Opened 6 years ago

Last modified 6 years ago

unsafeCoerce# doesn't always fully coerce

Reported by: yeoh@… Owned by:
Priority: normal Milestone:
Component: Compiler (Type checker) Version: 6.6.1
Keywords: unsafeCoerce# Cc:
Operating System: Linux Architecture: x86
Type of failure: Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description

{-# OPTIONS_GHC -fglasgow-exts #-}

import GHC.Prim( unsafeCoerce# )

e1 = unsafeCoerce# (+)
e2 = unsafeCoerce# shows

e1 coerces fine, even with the Num constraint, but e2 does not. The error for e2 is

    Ambiguous type variable `a' in the constraint:
      `Show a' arising from use of `shows' at T.lhs:7:20-24
    Probable fix: add a type signature that fixes these type variable(s)

Weirder still, ghci coerces shows fine:

*Main> :t unsafeCoerce# shows
unsafeCoerce# shows :: forall b. b

Change History

Changed 6 years ago by sorear

Not a bug, but merely an extremely obscure feature of Haskell's type system.

Because unsafeCoerce# does not impose a specific type upon the use of (+) or shows, the polymorphic type variable becomes ambiguous. However, since both (+) and shows need to know the actual type of use (due to the implicit dictionary argument), this variable needs to be resolved somehow, or an error produced.

Section 4.3.4 of the Haskell 98 Language and Libraries Report (  http://haskell.org/onlinereport/decls.html#sect4.3.4 ) specifies that ambiguous type variables are resolved using a default list, but only if (a) no non-standard classes are used and (b) at least one numeric class is used. The idea behind this was to make numeric code easier, as even 'show 2' would otherwise result in a type error. Thus, in Haskell 98 (with a unsafeCoerce primitive), the first example is legal and the second is not (since defaulting does not apply unless numeric classes are involved.) However, even the Haskell98 defaulting rules proved somewhat too onerous for REPL-type situations, so GHC provides a -fextended-default-rules flag (  http://haskell.org/ghc/dist/current/docs/users_guide/interactive-evaluation.html#extended-default-rules ) flag to allow more cases, including your second one.

Hopefully this clears things up.

Stefan

Changed 6 years ago by yeoh@…

I was going to add the comment below, but you've beat me to it.

Adding -fextended-default-rules definitely makes it all work, in ways I still don't fully understand. There is no ambiguity in the forall a b. a -> b explicit type signature, but the error message suggests that the typechecker refuses to surrender the Show a constraint. Am I mistaken in this interpretation?

If this is expected behavior, perhaps an error message suggesting -fextended-default-rules may assist the next unwary bystander.


Here's what I mean by not fully coercing. This typechecks:

{-# OPTIONS_GHC -fglasgow-exts -fno-monomorphism-restriction #-}

import GHC.Prim( unsafeCoerce# )

e2 = unsafeCoerce# (shows::a -> String -> String) :: 
     forall a b. Show a => a -> b

However, the Show a constraint must be carried around all the time which of course, severely hampers the extent of the coercion. Omitting the constraint gives me

    No instance for (Show a)
      arising from use of `shows' at T.lhs:6:20-24
    Possible fix: add (Show a) to the expected type of an expression
    In the first argument of `unsafeCoerce#', namely
        `(shows :: a -> String -> String)'
    In the expression:
          unsafeCoerce# (shows :: a -> String -> String) ::
            forall a b. a -> b
    In the definition of `e2':
        e2 = unsafeCoerce# (shows :: a -> String -> String) ::
               forall a b. a -> b

Changed 6 years ago by Isaac Dupree

LOL, I accidentally left out the ":t" in GHCi, and got (same result on x86 6.6.1)

> unsafeCoerce# shows
<interactive>: internal error: PAP object entered!
    (GHC version 6.6.1 for powerpc_unknown_linux)
    Please report this as a GHC bug:  http://www.haskell.org/ghc/reportabug
Aborted (core dumped)

What kind of showing is GHC trying to do on an object of type (forall b. b) anyway?

Passing -fno-extended-default-rules didn't work for me to disable that in ghci, so I went to a .hs file:

No, (shows::a -> String -> String) doesn't typecheck. (forall a. a -> String -> String) isn't a type of shows, only (forall a. Show a => a -> String -> String) is.

f :: forall a. a -> String -> String
f = undefined
e1 = ( unsafeCoerce# ) f

typechecks. Type variables that could be anything at all, don't need to be instantiated.

e2 = (unsafeCoerce# :: (forall a. Show a => a -> String -> String) -> b) shows

typechecks. GHC can't infer a rank-2 type for unsafeCoerce, but you can give it one (forall b. (forall a. Show a => a -> String -> String) -> b is clearly a more-specific version of unsafeCoerce's type forall a b. a -> b). This allows the polymorphic shows to be passed to unsafeCoerce, so that shows object can then be coerced as normal. (Rank-two types with typeclasses aren't nice, you always have to specify a new type signature when the set of type-classes is different...)

Neither -fno-monomorphism-restriction nor defaulting are needed.

Note that there is a difference between (unsafeCoerce# :: (Integer -> String -> String) -> b) shows (or whatever the type variable would default to, if not Integer) and (unsafeCoerce# :: (forall a. Show a => a -> String -> String) -> b) shows . Their representations are not the same; the second one can only be coerced back to the fully polymorphic version, and the first one can only be coerced back to the Integer version (well, probably if you wanted to pass _|_ for that parameter then you could also coerce the second one to have that parameter's type be anything you wanted, including polymorphic, as long as it wasn't qualified with a type-class constraint).

Changed 6 years ago by Isaac Dupree

oops, near the end, "probably...you could also coerce the second one" should be "the first one", rather (that is, the non-rank-2 version)

Changed 6 years ago by igloo

  • status changed from new to closed
  • resolution set to invalid

Defaulting only happens in H98 if you have a Num constraint. If you use -fextended-default-rules then the original example works:

{-# OPTIONS_GHC -fglasgow-exts -fextended-default-rules #-}

import GHC.Prim( unsafeCoerce# )

e1 = unsafeCoerce# (+)
e2 = unsafeCoerce# shows

Alternatively (and the preferred option), you can give shows an explicit type, so no defaulting needs to happen:

{-# OPTIONS_GHC -fglasgow-exts #-}

import GHC.Prim( unsafeCoerce# )

e1 = unsafeCoerce# (+)
e2 = unsafeCoerce# (shows :: () -> ShowS)
Note: See TracTickets for help on using tickets.