incoherent instance question

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

incoherent instance question

Graham Gill
Please see the paste: https://pastebin.com/zBim7Zkx

I'm experimenting with defining UpperBounded and LowerBounded typeclasses. An example type belonging to the latter that is not also Bounded would be type Natural from Numeric.Natural.

I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded. If a type is both UpperBounded and LowerBounded, then it is also Bounded.

To express the constraints, I need FlexibleInstances and UndecidableInstances extensions. These allow the module to load into ghci (8.4.2) with only a warning, but, without the INCOHERENT pragmas, I get an overlapping instance error if I try to evaluate minBound, maxBound, upperBound or lowerBound instantiated to either of the types Foo or Bar.

A solution is to apply the INCOHERENT pragma to the instances at lines 11, 14 and 17. Reading over section 10.8.3.6. Overlapping instances in the GHC User Guide, I believe I understand. (Is there a better solution?)

In the paste, I have INCOHERENT pragmas only at lines 11 and 17. This gives me the following behaviour in ghci:
  1. minBound, maxBound, upperBound and lowerBound instantiated to type Foo all function as expected, evaluating to the appropriate lower or upper bound.
  2. upperBound and maxBound instantiated at Bar give overlapping instance errors for UpperBounded, as expected.
  3. lowerBound :: Bar evaluates to C, as expected.
  4. minBound :: Bar gives an overlapping instance error for UpperBounded:
*UpperLowerBounded> minBound :: Bar

<interactive>:141:1: error:
   • Overlapping instances for UpperBounded Bar
       arising from a use of ‘minBound’
     Matching instances:
       instance [safe] Bounded a => UpperBounded a
         -- Defined at UpperLowerBounded.hs:14:10
       instance [safe] UpperBounded Bar -- Defined at UpperLowerBounded.hs:31:10
   • In the expression: minBound :: Bar
     In an equation for ‘it’: it = minBound :: Bar


It's #4 that I don't understand. An explanation would be very much appreciated. (Also, what's a [safe] instance?)

Regards,
Graham


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: incoherent instance question

Alex Rozenshteyn
I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded.

Seems reasonable
 
If a type is both UpperBounded and LowerBounded, then it is also Bounded.

Danger Will Robinson. Nothing stops modules (realistically, in two different library) from defining incompatible UpperBounded and LowerBounded instances; for example, I may want `lowerBound :: Bool` to be `True`, while you may want `upperBound :: Bool` to be `True`; when they are both imported, bad things can happen. In this case, requiring an Ord constraint and adding documentation on lawful instances would pretty much solve the problem, but in general, this is an unwise thing to do.

Specifically, it is usually a bad idea to have a type class that should have an instance for a type whenever that type has instances of some combination of other classes.

Three ways around it:
  • Use a newtype wrapper: define `newtype LowerUpperBounded` and have the instance be `(LowerBounded a, UpperBounded a) => Bounded (LowerUpperBounded a)`
  • Defer instance definition to concrete types: if you know that a specific type has both super-instances, you can explicitly instantiate it at the sub-instance
  • Define a constraint alias: (this requires some more advanced extensions, so I'm only mentioning it as an option for completeness' sake) Using `ConstraintKinds`, you can define `type Bounded a = (LowerBounded a, UpperBounded a)`; this makes the two definitions synonymous.

To your immediate question, I think what's happening is that when you're trying to do `minBound :: Bar` it looks for an instance `Bounded Bar` and finds the unique one; then it needs to satisfy the constraints, so it looks for `LowerBounded Bar` and `UpperBounded Bar`, the latter of which has two possible instances, neither of which is marked overlapping or incoherent.

You'll notice that if you use `-fdefer-type-errors` you will be able to get the `minBound` of `Bar` (though for some reason you need to bind it to a variable), but not the `maxBound`.

You should also note that if you use the `OVERLAPPABLE` pragma rather than the `INCOHERENT` one, you get the same results, and that is generally considered less risky.

On Sun, Jun 3, 2018 at 8:43 PM Graham Gill <[hidden email]> wrote:
Please see the paste: https://pastebin.com/zBim7Zkx

I'm experimenting with defining UpperBounded and LowerBounded typeclasses. An example type belonging to the latter that is not also Bounded would be type Natural from Numeric.Natural.

I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded. If a type is both UpperBounded and LowerBounded, then it is also Bounded.

To express the constraints, I need FlexibleInstances and UndecidableInstances extensions. These allow the module to load into ghci (8.4.2) with only a warning, but, without the INCOHERENT pragmas, I get an overlapping instance error if I try to evaluate minBound, maxBound, upperBound or lowerBound instantiated to either of the types Foo or Bar.

A solution is to apply the INCOHERENT pragma to the instances at lines 11, 14 and 17. Reading over section 10.8.3.6. Overlapping instances in the GHC User Guide, I believe I understand. (Is there a better solution?)

In the paste, I have INCOHERENT pragmas only at lines 11 and 17. This gives me the following behaviour in ghci:
  1. minBound, maxBound, upperBound and lowerBound instantiated to type Foo all function as expected, evaluating to the appropriate lower or upper bound.
  2. upperBound and maxBound instantiated at Bar give overlapping instance errors for UpperBounded, as expected.
  3. lowerBound :: Bar evaluates to C, as expected.
  4. minBound :: Bar gives an overlapping instance error for UpperBounded:
*UpperLowerBounded> minBound :: Bar

<interactive>:141:1: error:
   • Overlapping instances for UpperBounded Bar
       arising from a use of ‘minBound’
     Matching instances:
       instance [safe] Bounded a => UpperBounded a
         -- Defined at UpperLowerBounded.hs:14:10
       instance [safe] UpperBounded Bar -- Defined at UpperLowerBounded.hs:31:10
   • In the expression: minBound :: Bar
     In an equation for ‘it’: it = minBound :: Bar


It's #4 that I don't understand. An explanation would be very much appreciated. (Also, what's a [safe] instance?)

Regards,
Graham

_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners

_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: incoherent instance question

David McBride
In reply to this post by Graham Gill
This is a common thing that people try to do.  I want class A to apply to any type in which class B already applies.  It seems to mimic what would work in object oriented programming and it is hard to see at first why it doesn't work in haskell.

Just remember that they are "typeclasses", not "classclasses".  When you write

class Foo a where foo :: ...
instance Show a => Foo a where foo = something

Everything seems fine, but then you could write additional classes like this

instance Read a => Foo a where foo = something_else

And what if you had a type that is both a Read and Show, like Int?  Now there are two different things it could do -- something and something_else.  How to decide?  Based on order?  But then the behavior of the program could dramatically change based on the import order.

I would advise you to treat Bounded, UpperBounded, and LowerBounded to be separate properties and define them on all types explicitly rather than trying to obtain instances for free.

On Sun, Jun 3, 2018 at 11:42 PM, Graham Gill <[hidden email]> wrote:
Please see the paste: https://pastebin.com/zBim7Zkx

I'm experimenting with defining UpperBounded and LowerBounded typeclasses. An example type belonging to the latter that is not also Bounded would be type Natural from Numeric.Natural.

I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded. If a type is both UpperBounded and LowerBounded, then it is also Bounded.

To express the constraints, I need FlexibleInstances and UndecidableInstances extensions. These allow the module to load into ghci (8.4.2) with only a warning, but, without the INCOHERENT pragmas, I get an overlapping instance error if I try to evaluate minBound, maxBound, upperBound or lowerBound instantiated to either of the types Foo or Bar.

A solution is to apply the INCOHERENT pragma to the instances at lines 11, 14 and 17. Reading over section 10.8.3.6. Overlapping instances in the GHC User Guide, I believe I understand. (Is there a better solution?)

In the paste, I have INCOHERENT pragmas only at lines 11 and 17. This gives me the following behaviour in ghci:
  1. minBound, maxBound, upperBound and lowerBound instantiated to type Foo all function as expected, evaluating to the appropriate lower or upper bound.
  2. upperBound and maxBound instantiated at Bar give overlapping instance errors for UpperBounded, as expected.
  3. lowerBound :: Bar evaluates to C, as expected.
  4. minBound :: Bar gives an overlapping instance error for UpperBounded:
*UpperLowerBounded> minBound :: Bar

<interactive>:141:1: error:
   • Overlapping instances for UpperBounded Bar
       arising from a use of ‘minBound’
     Matching instances:
       instance [safe] Bounded a => UpperBounded a
         -- Defined at UpperLowerBounded.hs:14:10
       instance [safe] UpperBounded Bar -- Defined at UpperLowerBounded.hs:31:10
   • In the expression: minBound :: Bar
     In an equation for ‘it’: it = minBound :: Bar


It's #4 that I don't understand. An explanation would be very much appreciated. (Also, what's a [safe] instance?)

Regards,
Graham


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners



_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: incoherent instance question

Graham Gill
In reply to this post by Alex Rozenshteyn
Thank you Alex for that comprehensive answer. That's very helpful. I'd considered a newtype, but I really wondered what prevented me from using or was bad about the approach I described.

Regards,
Graham

On 04-Jun-2018 1:10 AM, Alex Rozenshteyn wrote:
I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded.

Seems reasonable
 
If a type is both UpperBounded and LowerBounded, then it is also Bounded.

Danger Will Robinson. Nothing stops modules (realistically, in two different library) from defining incompatible UpperBounded and LowerBounded instances; for example, I may want `lowerBound :: Bool` to be `True`, while you may want `upperBound :: Bool` to be `True`; when they are both imported, bad things can happen. In this case, requiring an Ord constraint and adding documentation on lawful instances would pretty much solve the problem, but in general, this is an unwise thing to do.

Specifically, it is usually a bad idea to have a type class that should have an instance for a type whenever that type has instances of some combination of other classes.

Three ways around it:
  • Use a newtype wrapper: define `newtype LowerUpperBounded` and have the instance be `(LowerBounded a, UpperBounded a) => Bounded (LowerUpperBounded a)`
  • Defer instance definition to concrete types: if you know that a specific type has both super-instances, you can explicitly instantiate it at the sub-instance
  • Define a constraint alias: (this requires some more advanced extensions, so I'm only mentioning it as an option for completeness' sake) Using `ConstraintKinds`, you can define `type Bounded a = (LowerBounded a, UpperBounded a)`; this makes the two definitions synonymous.

To your immediate question, I think what's happening is that when you're trying to do `minBound :: Bar` it looks for an instance `Bounded Bar` and finds the unique one; then it needs to satisfy the constraints, so it looks for `LowerBounded Bar` and `UpperBounded Bar`, the latter of which has two possible instances, neither of which is marked overlapping or incoherent.

You'll notice that if you use `-fdefer-type-errors` you will be able to get the `minBound` of `Bar` (though for some reason you need to bind it to a variable), but not the `maxBound`.

You should also note that if you use the `OVERLAPPABLE` pragma rather than the `INCOHERENT` one, you get the same results, and that is generally considered less risky.

On Sun, Jun 3, 2018 at 8:43 PM Graham Gill <[hidden email]> wrote:
Please see the paste: https://pastebin.com/zBim7Zkx

I'm experimenting with defining UpperBounded and LowerBounded typeclasses. An example type belonging to the latter that is not also Bounded would be type Natural from Numeric.Natural.

I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded. If a type is both UpperBounded and LowerBounded, then it is also Bounded.

To express the constraints, I need FlexibleInstances and UndecidableInstances extensions. These allow the module to load into ghci (8.4.2) with only a warning, but, without the INCOHERENT pragmas, I get an overlapping instance error if I try to evaluate minBound, maxBound, upperBound or lowerBound instantiated to either of the types Foo or Bar.

A solution is to apply the INCOHERENT pragma to the instances at lines 11, 14 and 17. Reading over section 10.8.3.6. Overlapping instances in the GHC User Guide, I believe I understand. (Is there a better solution?)

In the paste, I have INCOHERENT pragmas only at lines 11 and 17. This gives me the following behaviour in ghci:
  1. minBound, maxBound, upperBound and lowerBound instantiated to type Foo all function as expected, evaluating to the appropriate lower or upper bound.
  2. upperBound and maxBound instantiated at Bar give overlapping instance errors for UpperBounded, as expected.
  3. lowerBound :: Bar evaluates to C, as expected.
  4. minBound :: Bar gives an overlapping instance error for UpperBounded:
*UpperLowerBounded> minBound :: Bar

<interactive>:141:1: error:
   • Overlapping instances for UpperBounded Bar
       arising from a use of ‘minBound’
     Matching instances:
       instance [safe] Bounded a => UpperBounded a
         -- Defined at UpperLowerBounded.hs:14:10
       instance [safe] UpperBounded Bar -- Defined at UpperLowerBounded.hs:31:10
   • In the expression: minBound :: Bar
     In an equation for ‘it’: it = minBound :: Bar


It's #4 that I don't understand. An explanation would be very much appreciated. (Also, what's a [safe] instance?)

Regards,
Graham

_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners



_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: incoherent instance question

Graham Gill
In reply to this post by David McBride
Thanks for the explanation David. The problem is clear from your description, and when you *really* do want to do it anyway I guess that's what the INCOHERENT and OVERLAPS/OVERLAPPING pragmas are for, to help you control the types.

Just remember that they are "typeclasses", not "classclasses".

Actually I wasn't thinking in terms of "classclasses", instead, of mathematical equivalences. A set of reals is bounded iff it is both upper and lower bounded. I wanted to try to express that in types, and wondered if I could do it without resorting to asymmetrical syntax (the newtype suggestion).

Regards,
Graham


On Mon, Jun 4, 2018 at 8:54 AM David McBride <[hidden email]> wrote:
This is a common thing that people try to do.  I want class A to apply to any type in which class B already applies.  It seems to mimic what would work in object oriented programming and it is hard to see at first why it doesn't work in haskell.

Just remember that they are "typeclasses", not "classclasses".  When you write

class Foo a where foo :: ...
instance Show a => Foo a where foo = something

Everything seems fine, but then you could write additional classes like this

instance Read a => Foo a where foo = something_else

And what if you had a type that is both a Read and Show, like Int?  Now there are two different things it could do -- something and something_else.  How to decide?  Based on order?  But then the behavior of the program could dramatically change based on the import order.

I would advise you to treat Bounded, UpperBounded, and LowerBounded to be separate properties and define them on all types explicitly rather than trying to obtain instances for free.

On Sun, Jun 3, 2018 at 11:42 PM, Graham Gill <[hidden email]> wrote:
Please see the paste: https://pastebin.com/zBim7Zkx

I'm experimenting with defining UpperBounded and LowerBounded typeclasses. An example type belonging to the latter that is not also Bounded would be type Natural from Numeric.Natural.

I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded. If a type is both UpperBounded and LowerBounded, then it is also Bounded.

To express the constraints, I need FlexibleInstances and UndecidableInstances extensions. These allow the module to load into ghci (8.4.2) with only a warning, but, without the INCOHERENT pragmas, I get an overlapping instance error if I try to evaluate minBound, maxBound, upperBound or lowerBound instantiated to either of the types Foo or Bar.

A solution is to apply the INCOHERENT pragma to the instances at lines 11, 14 and 17. Reading over section 10.8.3.6. Overlapping instances in the GHC User Guide, I believe I understand. (Is there a better solution?)

In the paste, I have INCOHERENT pragmas only at lines 11 and 17. This gives me the following behaviour in ghci:
  1. minBound, maxBound, upperBound and lowerBound instantiated to type Foo all function as expected, evaluating to the appropriate lower or upper bound.
  2. upperBound and maxBound instantiated at Bar give overlapping instance errors for UpperBounded, as expected.
  3. lowerBound :: Bar evaluates to C, as expected.
  4. minBound :: Bar gives an overlapping instance error for UpperBounded:
*UpperLowerBounded> minBound :: Bar

<interactive>:141:1: error:
   • Overlapping instances for UpperBounded Bar
       arising from a use of ‘minBound’
     Matching instances:
       instance [safe] Bounded a => UpperBounded a
         -- Defined at UpperLowerBounded.hs:14:10
       instance [safe] UpperBounded Bar -- Defined at UpperLowerBounded.hs:31:10
   • In the expression: minBound :: Bar
     In an equation for ‘it’: it = minBound :: Bar


It's #4 that I don't understand. An explanation would be very much appreciated. (Also, what's a [safe] instance?)

Regards,
Graham


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners

_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: incoherent instance question

Alex Rozenshteyn
mathematical equivalences

Yeah... Unfortunately, type classes aren't good at those. They're good for necessary conditions, and you can make them work for necessary and sufficient conditions, but only if you control the class in question. One major problem in your attempt is that you're not defining Bounded yourself, so you can't define necessary conditions (since those would have to be part of the class). If you controlled Bounded, you could do this:

class UpperBounded a where ...
class LowerBounded a where ...
class (UpperBounded a, LowerBounded a) => Bounded a -- This is the "necessary" part; notice no "..."
instance (UpperBounded a, LowerBounded a) => Bounded a -- This is the "sufficient" part

You'd need to turn on some extensions (UndecidableInstances and FlexibleContexts, IIRC).

On Fri, Jun 8, 2018 at 6:24 PM, Graham Gill <[hidden email]> wrote:
Thanks for the explanation David. The problem is clear from your description, and when you *really* do want to do it anyway I guess that's what the INCOHERENT and OVERLAPS/OVERLAPPING pragmas are for, to help you control the types.

Just remember that they are "typeclasses", not "classclasses".

Actually I wasn't thinking in terms of "classclasses", instead, of mathematical equivalences. A set of reals is bounded iff it is both upper and lower bounded. I wanted to try to express that in types, and wondered if I could do it without resorting to asymmetrical syntax (the newtype suggestion).

Regards,
Graham


On Mon, Jun 4, 2018 at 8:54 AM David McBride <[hidden email]> wrote:
This is a common thing that people try to do.  I want class A to apply to any type in which class B already applies.  It seems to mimic what would work in object oriented programming and it is hard to see at first why it doesn't work in haskell.

Just remember that they are "typeclasses", not "classclasses".  When you write

class Foo a where foo :: ...
instance Show a => Foo a where foo = something

Everything seems fine, but then you could write additional classes like this

instance Read a => Foo a where foo = something_else

And what if you had a type that is both a Read and Show, like Int?  Now there are two different things it could do -- something and something_else.  How to decide?  Based on order?  But then the behavior of the program could dramatically change based on the import order.

I would advise you to treat Bounded, UpperBounded, and LowerBounded to be separate properties and define them on all types explicitly rather than trying to obtain instances for free.

On Sun, Jun 3, 2018 at 11:42 PM, Graham Gill <[hidden email]> wrote:
Please see the paste: https://pastebin.com/zBim7Zkx

I'm experimenting with defining UpperBounded and LowerBounded typeclasses. An example type belonging to the latter that is not also Bounded would be type Natural from Numeric.Natural.

I want to say that if a type is Bounded, then it is also UpperBounded and LowerBounded. If a type is both UpperBounded and LowerBounded, then it is also Bounded.

To express the constraints, I need FlexibleInstances and UndecidableInstances extensions. These allow the module to load into ghci (8.4.2) with only a warning, but, without the INCOHERENT pragmas, I get an overlapping instance error if I try to evaluate minBound, maxBound, upperBound or lowerBound instantiated to either of the types Foo or Bar.

A solution is to apply the INCOHERENT pragma to the instances at lines 11, 14 and 17. Reading over section 10.8.3.6. Overlapping instances in the GHC User Guide, I believe I understand. (Is there a better solution?)

In the paste, I have INCOHERENT pragmas only at lines 11 and 17. This gives me the following behaviour in ghci:
  1. minBound, maxBound, upperBound and lowerBound instantiated to type Foo all function as expected, evaluating to the appropriate lower or upper bound.
  2. upperBound and maxBound instantiated at Bar give overlapping instance errors for UpperBounded, as expected.
  3. lowerBound :: Bar evaluates to C, as expected.
  4. minBound :: Bar gives an overlapping instance error for UpperBounded:
*UpperLowerBounded> minBound :: Bar

<interactive>:141:1: error:
   • Overlapping instances for UpperBounded Bar
       arising from a use of ‘minBound’
     Matching instances:
       instance [safe] Bounded a => UpperBounded a
         -- Defined at UpperLowerBounded.hs:14:10
       instance [safe] UpperBounded Bar -- Defined at UpperLowerBounded.hs:31:10
   • In the expression: minBound :: Bar
     In an equation for ‘it’: it = minBound :: Bar


It's #4 that I don't understand. An explanation would be very much appreciated. (Also, what's a [safe] instance?)

Regards,
Graham


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners


_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners

_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners



_______________________________________________
Beginners mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners