Avoiding undecidables

Hi all, Well, I've got two problems which both want to be solved with undecidable and overlapping instances. Obviously, I'd like to try and avoid them. For the record, the problems have to do with the control-monad-failure and convertible packages. The code below *should* make clear what I'm trying to accomplish: -- failure should not be limited to just monads, so... class Failure e f where failure :: e -> f v class (Functor f, Failure e f) => FunctorFailure e f instance (Functor f, Failure e f) => FunctorFailure e f -- undecidable, overlaps class (Applicative f, Failure e f) => ApplicativeFailure e f instance (Applicative f, Failure e f) => ApplicativeFailure e f -- undecidable, overlaps class (Monad f, Failure e f) => MonadFailure e f instance (Monad f, Failure e f) => MonadFailure e f -- undecidable, overlaps And now the convertible issue. I want two type classes: Convert for anything which *might* be convertible, but might not. For example, sometimes a String can be converted to an Int (like the string "5"), but sometimes it will fail (like "five"). TotalConvert is when something *is* convertible, such as Int to String (simply the show function). Thus the following: class Convert x y where convert :: x -> Maybe y class Convert x y => TotalConvert x y where totalConvert :: x -> y instance TotalConvert x y => Convert x y where -- Boom! convert = Just . totalConvert Any ideas are most welcome. Both of these seem like cases where the compiler could do some DWIMery, but isn't. Michael

On Dec 5, 2009, at 6:58 PM, Michael Snoyman wrote:
Hi all,
Well, I've got two problems which both want to be solved with undecidable and overlapping instances. Obviously, I'd like to try and avoid them. For the record, the problems have to do with the control-monad-failure and convertible packages. The code below *should* make clear what I'm trying to accomplish:
-- failure should not be limited to just monads, so... class Failure e f where failure :: e -> f v class (Functor f, Failure e f) => FunctorFailure e f instance (Functor f, Failure e f) => FunctorFailure e f -- undecidable, overlaps class (Applicative f, Failure e f) => ApplicativeFailure e f instance (Applicative f, Failure e f) => ApplicativeFailure e f -- undecidable, overlaps class (Monad f, Failure e f) => MonadFailure e f instance (Monad f, Failure e f) => MonadFailure e f -- undecidable, overlaps
(Functor|Monad|Applicative)Failure are little more than class synonyms, right ? Or equivalently, do you envision the need of writing, say, a MonadFailure instance for a type A which which works differently than the existing Failure instance for A? If the answer is no as I presume, then you don't want overlapping instances. Regarding undecidable instances, I will say that from my point of view they do not constitute a language extension. MPTCs are the language extension here. Since MPTCs can lead to non-terminating type checking, a compiler can either allow any use of them, employ a termination prover to ensure that only well-behaved instances are defined, or impose a set of restrictions that ensure termination. GHC does the latter, rather conservatively in some cases, and undecidable instances is just a compiler flag that puts the burden of termination checking on the user. In this case it is obvious that non-termination is not going to be a problem for the instances defined above. Since you have already decided MPTCs are ok, in my opinion undecidable instances are fine here. But I realize this is not the usual stance, so I might be wrong.
And now the convertible issue. I want two type classes: Convert for anything which *might* be convertible, but might not. For example, sometimes a String can be converted to an Int (like the string "5"), but sometimes it will fail (like "five"). TotalConvert is when something *is* convertible, such as Int to String (simply the show function). Thus the following:
class Convert x y where convert :: x -> Maybe y class Convert x y => TotalConvert x y where totalConvert :: x -> y instance TotalConvert x y => Convert x y where -- Boom! convert = Just . totalConvert
In this case Convert is not just a class synonym, so you are going to run into trouble with that code. I would do this as follows: class Convert x y => TotalConvert x y where totalConvert :: x -> y totalConvert = fromJust . convert For instance, instance Convert Integer Double where convert = Just . fromIntegral instance TotalConvert Double -- that's all Cheers, pepe

On Sat, Dec 5, 2009 at 10:33 PM, José Iborra
On Dec 5, 2009, at 6:58 PM, Michael Snoyman wrote:
Hi all,
Well, I've got two problems which both want to be solved with undecidable and overlapping instances. Obviously, I'd like to try and avoid them. For the record, the problems have to do with the control-monad-failure and convertible packages. The code below *should* make clear what I'm trying to accomplish:
-- failure should not be limited to just monads, so... class Failure e f where failure :: e -> f v class (Functor f, Failure e f) => FunctorFailure e f instance (Functor f, Failure e f) => FunctorFailure e f -- undecidable, overlaps class (Applicative f, Failure e f) => ApplicativeFailure e f instance (Applicative f, Failure e f) => ApplicativeFailure e f -- undecidable, overlaps class (Monad f, Failure e f) => MonadFailure e f instance (Monad f, Failure e f) => MonadFailure e f -- undecidable, overlaps
(Functor|Monad|Applicative)Failure are little more than class synonyms, right ? Or equivalently, do you envision the need of writing, say, a MonadFailure instance for a type A which which works differently than the existing Failure instance for A? If the answer is no as I presume, then you don't want overlapping instances.
Regarding undecidable instances, I will say that from my point of view they do not constitute a language extension. MPTCs are the language extension here. Since MPTCs can lead to non-terminating type checking, a compiler can either allow any use of them, employ a termination prover to ensure that only well-behaved instances are defined, or impose a set of restrictions that ensure termination. GHC does the latter, rather conservatively in some cases, and undecidable instances is just a compiler flag that puts the burden of termination checking on the user.
In this case it is obvious that non-termination is not going to be a problem for the instances defined above. Since you have already decided MPTCs are ok, in my opinion undecidable instances are fine here. But I realize this is not the usual stance, so I might be wrong.
Sounds reasonable to me. I'm waiting for the boogey man to jump out though
and explain why undecidables here will get me gored to death by a raptor [1].
And now the convertible issue. I want two type classes: Convert for anything which *might* be convertible, but might not. For example, sometimes a String can be converted to an Int (like the string "5"), but sometimes it will fail (like "five"). TotalConvert is when something *is* convertible, such as Int to String (simply the show function). Thus the following:
class Convert x y where convert :: x -> Maybe y class Convert x y => TotalConvert x y where totalConvert :: x -> y instance TotalConvert x y => Convert x y where -- Boom! convert = Just . totalConvert
In this case Convert is not just a class synonym, so you are going to run into trouble with that code.
I would do this as follows:
class Convert x y => TotalConvert x y where totalConvert :: x -> y totalConvert = fromJust . convert
For instance,
instance Convert Integer Double where convert = Just . fromIntegral instance TotalConvert Double -- that's all
Interestng approach. The current approach is *almost* the same, just leaves
off the default definition. I'd be wary of putting in a definition like that; although it saves a line of typing, it let's a partial function get in which could cause trouble for unsuspecting users. But it is a possibility.
Cheers, pepe

Am Samstag 05 Dezember 2009 21:51:26 schrieb Michael Snoyman:
On Sat, Dec 5, 2009 at 10:33 PM, José Iborra
wrote: Since you have already decided MPTCs are ok, in my opinion undecidable instances are fine here. But I realize this is not the usual stance, so I might be wrong.
As far as I understand, all UndecidableInstances does is tell the compiler "Maybe instance checking won't terminate, try anyway", so they're dangerous *only during compilation*, once things compile, everything's dandy.
Sounds reasonable to me. I'm waiting for the boogey man to jump out though
and explain why undecidables here will get me gored to death by a raptor [1].
They won't. They might suck you into a black hole, but raptors are very specialised.
instance Convert Integer Double where convert = Just . fromIntegral instance TotalConvert Double -- that's all
instance TotalConvert Integer Double
Interestng approach. The current approach is *almost* the same, just leaves
off the default definition. I'd be wary of putting in a definition like that; although it saves a line of typing, it let's a partial function get in which could cause trouble for unsuspecting users. But it is a possibility.
But only if they declare misbehaved instances of TotalConvert. Whenever a reasonable instance TotalConvert x y exists, the Convert instance should satisfy the demands of the default definition.
Cheers, pepe
participants (3)
-
Daniel Fischer
-
José Iborra
-
Michael Snoyman