Opened 5 years ago

Last modified 2 years ago

#7543 new bug

Constraint synonym instances

Reported by: monoidal Owned by:
Priority: normal Milestone:
Component: Compiler Version: 7.6.1
Keywords: Cc: ekmett@…
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

It would be great if GHC could compile:

{-# LANGUAGE ConstraintKinds #-}

type Ring = Num

instance Ring [a] where
  (+) = (++)

Currently this gives an error: (+) is not a visible method of class Ring. After removing the last line, the code compiles with warnings about missing methods.

Change History (13)

comment:1 Changed 5 years ago by simonpj

difficulty: Unknown

I can see your point; and I agree it's odd that it's accepted when no methods are given. But it's not easy to fix in the way you want. Here's why.

Suppose we have

class C a where
  reverse :: a -> a
instance C Int where
  reverse x = x

When GHC encounters the binding reverse x = x, it has to figure out which reverse you mean -- there are two in scope. Ah! Since this is in an instnace declaraction, it must be the one that's method of class C, not the one from Prelude.

This scope resolution is done by the renamer. The renamer does not understand type synonyms (they are interpreted by the subsequent type checker), so it can't figure out that Ring really means Num in your example.

I can't see an easy way round this in GHC's current structure. The only think that comes to mind is to postpone all scope resolution for instance bindings until the type checker. But that would mean a bit of an upheaval of datatypes etc. Quite do-able, but not very simple.

As things stand it might be more consistent to reject an instance declaration if the "class" turns out to be a synonym.

I'm rather inclined to do nothing!

Simon

comment:2 Changed 5 years ago by ekmett

Cc: ekmett@… added

FWIW some form of fix for this would be quite useful to me as well.

In the lens package we have a lot of classes that look like

class Foo s t a b | s -> a, t -> b, s a -> t, t a -> s

where s = t, a = b are always legal to instantiate, so many instances look like

class IsText Text Text Char Char

when we're dealing with these 4 parameter families for data types we adopt the convention that

type Foo' a b = Foo a a b b

But we cannot do this for the declaration site for the instances.

If we could this would (eventually, when we can remove support for pre-constraint kinds compilers) enable us to more easily expand our definitions later on.

More pie-in-the-sky would be being able to use

type Foo a b = Bar a => Baz a b
instance Foo a b

as

instance Bar a => Baz a b

it would be consistent with the other uses of type, but Bar a => Baz a b doesn't (currently) have a sensible kind.

comment:3 Changed 5 years ago by igloo

Milestone: 7.8.1

comment:4 Changed 4 years ago by thoughtpolice

Milestone: 7.8.37.10.1

Moving to 7.10.1

comment:5 Changed 3 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:6 Changed 3 years ago by bheklilr

Has there been any more interest in this bug? I happened to run across it for the first time today with a problem essentially identical to @ekmett's. Any chance of it getting fixed one way or the other?

comment:7 Changed 3 years ago by goldfire

Building on comment:1, would it be easy to build this feature if the names of the methods have no scoping conflicts? That is, just do a normal (non-instance-style) lookup of method names, even in instances. If there is no ambiguity, proceed. If there is ambiguity, look at the instance head. If it's a class name, use that to disambiguate. Otherwise (if it's a synonym or bogus), report an error. That seems simple enough and without undue upheaval.

Personally, I don't think disambiguating among multiply-in-scope names should hold this feature up.

comment:8 Changed 3 years ago by simonpj

Yes we could do as Richard suggests. It'd be one more thing to explain in the user manual. And I'm a little suspicious, because now if I'm hunting for instances of some class I need to take synonyms into account.

I grant that we already allow type synonyms in the instance head, and that too makes hunting for instances harder in a similar way.

Are these disadvantages outweighed by the advantages? Do more than two people want this?

Simon

comment:9 Changed 3 years ago by goldfire

The other thing we could do is support this feature at synonym-definition time. When defining a type synonym such that the head of the RHS is a class, record this fact in the TyCon and corresponding iface info. If such a synonym appears as the head of an instance declaration, we use this tidbit to do the name lookup.

This would be easier to explain, at least, and wouldn't require much upheaval either. Actually, this is probably simpler than my idea in comment:7.

About looking up instances: we really need to move beyond grep for this sort of thing! Of course, I exclusively use grep for this sort of thing... because I don't have a better option to hand. But that's a story for another day. In the meantime, worrying about obfuscation via synonyms is valid, but also not something that (I believe) should hold this up.

comment:10 in reply to:  9 Changed 3 years ago by simonpj

This would be easier to explain, at least, and wouldn't require much upheaval either. Actually, this is probably simpler than my idea in comment:7.

I don't think so.

type C a = X a Num
type X a b = b a

I really don't want to put enough cleverness in the renamer to expand type synonyms on the fly.

Let's see how keen our users are.

Simon

comment:11 Changed 3 years ago by bheklilr

I don't have a particular attachment to being able to do this, my biggest concern is that it's somewhat inconsistent behavior. If a constraint isn't a class, then it shouldn't be possible to make an instance of it. Or we should allow all constraints to be instantiated as classes. This could lead to some interesting problems to have to solve, such as when two constraints are used that could have overlapping names

module C1 where

class C1 a where
    c :: a -> a

module C2 where

class C2 a where
    c :: a -> a

module C where

import C1
import C2
type Cs a = (C1 a, C2 a)

instance Cs Int where
    C1.c = pred
    C2.c = succ

This won't work currently because you can't use qualified names in a binding position, but without the qualification you can't distinguish between the different c methods on the classes. I also can't say I'm a fan of having multiple instances defined in the same block. For example, the following would bother me

newtype MyInt = MyInt Int

type OrdNum a = (Num a, Ord a)

instance OrdNum MyInt where
    compare (MyInt x) (MyInt y) = compare x y
    fromInteger = MyInt
    ...
-- Results in
-- instance Num MyInt
-- instance Ord MyInt

Considering that the argument for being able use constraints like this is that it would slightly improve the usability of a handful of libraries, I'm personally leaning towards a fix that would simply disallow instancing anything that is not a class.

comment:12 Changed 3 years ago by thoughtpolice

Milestone: 7.12.18.0.1

Milestone renamed

comment:13 Changed 2 years ago by thomie

Milestone: 8.0.1
Note: See TracTickets for help on using tickets.