Opened 7 years ago

Last modified 12 months ago

#7066 new bug

isInstance does not work for compound types

Reported by: edsko Owned by: simonpj
Priority: normal Milestone:
Component: Template Haskell Version: 7.4.2
Keywords: Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Consider

import Language.Haskell.TH.Syntax                                                
                                                                                 
data A = A                                                                       
                                                                                 
test :: Q [Dec]                                                                  
test = do                                                                        
  tp <- [t| (Int, A) |]                                                          
  inst1 <- isInstance ''Show [ tp ]                                              
  runIO $ print inst1                                                            
  return []  

This prints True even though there is no Show instance for A (the Show instance it finds is for pairs, but it does not verify that there are Show instances for the pair components).

Change History (16)

comment:1 Changed 7 years ago by simonpj

difficulty: Unknown
Owner: set to simonpj

That's true, and currently so by design. What if you did want to ask "is there an instance for pairs?" You could try making up fresh type variables 'x' and 'y', say, but then with the behaviour you were expecting it would try to find an instance for (Show x), fail, and hence return an empty list.

It should be better documented, I agree, but I'm not sure what alternative behaviour would make sense.

I need review and commit Reiner's patches from #5469, but then I'll add the docs.

comment:2 Changed 7 years ago by edsko

I see. Fair enough, but then it would be very useful if a separate function was provided that does have the "expected" behaviour for things like (Int, A) (for some definition of "expected" :). It's not obvious to me that such a function is easily user-definable.

comment:3 Changed 7 years ago by simonpj

Would you care to define the "expected" behaviour that you expect?

comment:4 Changed 7 years ago by edsko

Oh, I'm sorry. I would expect

isInstance clss typs

to return True if and only if I can safely generate code (in my Template Haskell code) that relies on 'typs' being an instance of 'clss'. In the example above, I can not safely generate code that relies on a Show instance for (Int, A) because there is no Show instance for A. However, I can safely generate code that relies on a Show instance for (Int, Bool), say.

Context: In Cloud Haskell when you call 'remotable' on a monomorphic function f :: T1 -> T2, I automatically generate a top-level definition

f__sdict :: Static (SerializableDict T1)

but if T1 is not in fact serializable then this will subsequently cause a type error. So I would like to check (using isInstance) if T1 is an instance of Serializable before generating this code.

comment:5 Changed 7 years ago by simonpj

What if the type contains type variables?

comment:6 Changed 7 years ago by edsko

Yes, I guess that's the tricky case -- but this problem already exists. isInstance just checks if reifyInstances doesn't return the empty list; so reifyInstances for Show (a, Int) could return

Instance (Show a) => Instance (a, Int)

just like it returns

Instance (Show a, Show b) => Instance (a, b)

now for that exact same argument. If 'isInstance' is not modified, then an answer of True would mean: this could be satisfied (if there are instances for the type variables), and for monomorphic types it would mean yes, this is satisfied.

comment:7 Changed 7 years ago by igloo

Milestone: 7.8.1

comment:8 Changed 5 years ago by mojojojo

I just got hit by this issue.

I would expect

isInstance clss typs

to return True if and only if I can safely generate code (in my Template Haskell code) that relies on 'typs' being an instance of 'clss'. In the example above, I can not safely generate code that relies on a Show instance for (Int, A) because there is no Show instance for A.

I expected exactly the same. That's why I believe that the current behaviour of the function is unintuitive to say the least. The documentation has no info concerning such behaviour either.

comment:9 Changed 5 years ago by mojojojo

I've just released "fixed" versions of reifyInstances and isInstance as the th-instance-reification library, which work as edsko and I expected.

comment:10 Changed 5 years ago by simonpj

Hmm:

  • Are you making the case for an actual change in the behaviour of the current TH operations? The current reifyInstances is deliberately primitive (a one-level lookup) so that other stuff can be built on top. Or are you asking for some new functions in the Quasi monad; or new functions in the template-haskell library?
  • It would help a lot to be clear precisely what the behaviour is supposed to be when the types concerned contain type variables. Currently reifyInstances returns all the instance whose heads unify with the specified constraint. I've looked at your code and it's not clear to me exactly what it does. Could you write a specification?
  • I think it's arguable that what you really want is something like
    isInstance :: Cxt -> Name -> [Type] -> Q Bool
    
    return True if the constraint (C tys) is provable from the specifed context. For example, you could ask, say isIntance [Show a, Num a] Foo [Maybe a], to ask whether you can prove (Foo (Maybe a)) from (Show a, Num a). For ground types you could give the empty Cxt.

Simon

comment:11 Changed 5 years ago by mojojojo

Are you making the case for an actual change in the behaviour of the current TH operations? The current reifyInstances is deliberately primitive (a one-level lookup) so that other stuff can be built on top. Or are you asking for some new functions in the Quasi monad; or new functions in the template-haskell library?

Well. I consider the current behaviour of reifyInstances counterintuitive, to say at very least. It took me quite some time of debugging the library I was developing at the time, before I narrowed the issue I was experiencing down to the unexpected behaviour of reifyInstances. Only then did I get to googling and finally here to find out that it's actually an intended behaviour, though misleadingly documented.

Let's consider the use cases for that function. All I can imagine is finding the instances, which then can be relied on as applicable to the tested types. I can imagine no scenario, in which one would want to get instances, which the typechecker would deny. Can you imagine any examples of such a case?

Considering the above, I can only see the current behaviour of that function as a bug. However I am not strongly opinionated about changing that, especially since I've already published a solution. The documentation however definitely should be updated to make the behaviour of this function clear.

It would help a lot to be clear precisely what the behaviour is supposed to be when the types concerned contain type variables. Currently reifyInstances returns all the instance whose heads unify with the specified constraint. I've looked at your code and it's not clear to me exactly what it does. Could you write a specification?

I've updated the code by covering it with extensive comments. The test suite should also be useful for clarifying the expected behaviour.

I think it's arguable that what you really want is something like isInstance :: Cxt -> Name -> [Type] -> Q Bool return True if the constraint (C tys) is provable from the specifed context. For example, you could ask, say isIntance [Show a, Num a] Foo [Maybe a], to ask whether you can prove (Foo (Maybe a)) from (Show a, Num a). For ground types you could give the empty Cxt.

Yes, this looks like it.

comment:12 Changed 5 years ago by thoughtpolice

Milestone: 7.8.37.10.1

Moving to 7.10.1.

comment:13 Changed 5 years ago by thoughtpolice

Milestone: 7.10.17.12.1

Moving to 7.12.1 milestone; if you feel this is an error and should be addressed sooner, please move it back to the 7.10.1 milestone.

comment:14 Changed 4 years ago by thoughtpolice

Milestone: 7.12.18.0.1

Milestone renamed

comment:15 Changed 4 years ago by thomie

Milestone: 8.0.1

comment:16 Changed 12 months ago by Ryan Scott <ryan.gl.scott@…>

In 85376570/ghc:

Documentation fixes in 'template-haskell'

Summary:
 * Clarify the non-presence of derived classes in reified decls (#15167)
 * Clarify the shallowness of "reifyInstances" (#7066)
 * Mention that 'Typeable' instances are not found by reifyInstances (#11251)
 * Various Haddock markup issues fixed

Reviewers: goldfire, bgamari

Reviewed By: bgamari

Subscribers: rwbarton, carter

GHC Trac Issues: #15167, #7066, #11251

Differential Revision: https://phabricator.haskell.org/D5197
Note: See TracTickets for help on using tickets.