Re: [Haskell-cafe] How to expose if a constraint is satisfiable

It's occurred to me that one could write a class C t which is satisfied
whenever (A t) or (B t) is satisfied like so:
Hi Clinton, this sounds like you might want "Choosing a type-class instance based on the context" https://wiki.haskell.org/GHC/AdvancedOverlap
---
data Satisfied
type family IsSatisfiable :: Constraint -> Type
That type family is doing the same job as auxiliary class `ShowPred` on that wiki page. Rather than all the machinery you give for the disjunction, you could use another type family: type family EitherSatisfied :: Type -> Type -> Type instance EitherSatisfied Satisfied tb = Satisfied instance EitherSatisfied ta Satisfied = Satisfied Those two instances do overlap (as you expected); and they exhibit confluence, aka coincident overlap (they produce the same result); so that's fine. But you haven't given any instances for `IsSatisfiable`. How do you expect to get from the Constraint to the `Satisfied` type? You say
IsSatisfiable c = Satisfied -- (if c is satisfiable)
What are you going to put for `c`? If you follow that wiki page, you'll need to in effect repeat every instance decl for classes `A, B`: instance A Int where ... type instance IsSatisfiable (A Int) = Satisfied (The wiki page was written before there were type families, so type class `ShowPred` has a Functional Dependency giving a type-level Boolean result.) Your `C t` class becomes class EitherSatisfied ( IsSatisfiable (A t)) ( IsSatisfiable (B t)) ~ Satisfied => C t where ... ---- Nowadays there's a better way: make Associated Types for the two classes, give them a default instance: class A t where type APred t type instance APred t = Satisfied ... class B t where type BPred t type instance BPred t = Satisfied ... Now every instance defined for `A, B` in effect automatically gives you `APred, BPred` instances. Then class EitherSatisifed (APred t) (BPred t) ~ Satisfied => C t where ... AntC

Hi Anthony Perhaps I've misunderstood, but there's a few issues with the approaches you suggest: Firstly, you refer to https://wiki.haskell.org/GHC/AdvancedOverlap. Unfortunately (unless I've missed something) these involve listing all the instances of parent classes. I'm trying to avoid that. Indeed if I have to explicitly list all the instances I might as well write them the normal way so I'm not sure what the point of any trickery is. I also tried the associated types approach you suggested towards the end of your email previously. This works perfectly fine if you can edit the base class, but I can't edit say, "Applicative" or "Num". I did something like the following, but I ran into a problem: {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleInstances #-} data Satisfied class Num t => MyNum t where type IsNum t instance Num t => MyNum t where type IsNum t = Satisfied data D = D f :: IsNum t ~ Satisfied => t -> () f _ = () main = print $ f D Ideally this should not compile, but unfortunately it happily compiles, showing that GHC generates an "IsNum" type family instance for "D", despite the fact that "Num D" is not satisfied. Any suggestions going forward from here? On Mon, May 7, 2018 at 9:11 PM, Anthony Clayden < anthony_clayden@clear.net.nz> wrote:
It's occurred to me that one could write a class C t which is satisfied
whenever (A t) or (B t) is satisfied like so:
Hi Clinton, this sounds like you might want "Choosing a type-class instance based on the context" https://wiki.haskell.org/GHC/AdvancedOverlap
---
data Satisfied
type family IsSatisfiable :: Constraint -> Type
That type family is doing the same job as auxiliary class `ShowPred` on that wiki page.
Rather than all the machinery you give for the disjunction, you could use another type family:
type family EitherSatisfied :: Type -> Type -> Type instance EitherSatisfied Satisfied tb = Satisfied instance EitherSatisfied ta Satisfied = Satisfied
Those two instances do overlap (as you expected); and they exhibit confluence, aka coincident overlap (they produce the same result); so that's fine.
But you haven't given any instances for `IsSatisfiable`. How do you expect to get from the Constraint to the `Satisfied` type?
You say
IsSatisfiable c = Satisfied -- (if c is satisfiable)
What are you going to put for `c`? If you follow that wiki page, you'll need to in effect repeat every instance decl for classes `A, B`:
instance A Int where ...
type instance IsSatisfiable (A Int) = Satisfied
(The wiki page was written before there were type families, so type class `ShowPred` has a Functional Dependency giving a type-level Boolean result.) Your `C t` class becomes
class EitherSatisfied ( IsSatisfiable (A t)) ( IsSatisfiable (B t)) ~ Satisfied => C t where ...
----
Nowadays there's a better way: make Associated Types for the two classes, give them a default instance:
class A t where type APred t type instance APred t = Satisfied ...
class B t where type BPred t
type instance BPred t = Satisfied ...
Now every instance defined for `A, B` in effect automatically gives you `APred, BPred` instances. Then
class EitherSatisifed (APred t) (BPred t) ~ Satisfied => C t where ...
AntC
_______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

On Mon, May 7, 2018 at 7:50 AM, Clinton Mead
class Num t => MyNum t where type IsNum t instance Num t => MyNum t where type IsNum t = Satisfied
This looks wrong to me: given how instance resolution works, I'd expect this to match every type and produce Satisfied, and later rejection based on the context wouldn't affect it. But I could well be thinking about it incorrectly. -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

Correct. That instance won't work as expected. On 5/7/2018 8:21 PM, Brandon Allbery wrote:
On Mon, May 7, 2018 at 7:50 AM, Clinton Mead
mailto:clintonmead@gmail.com> wrote: class Num t => MyNum t where type IsNum t instance Num t => MyNum t where type IsNum t = Satisfied
This looks wrong to me: given how instance resolution works, I'd expect this to match every type and produce Satisfied, and later rejection based on the context wouldn't affect it. But I could well be thinking about it incorrectly.
-- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com mailto:allbery.b@gmail.com ballbery@sinenomine.net mailto:ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
_______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

On Tue, 8 May 2018 at 12:20 AM, Clinton Mead
Firstly, you refer to https://wiki.haskell.org/GHC/AdvancedOverlap. Unfortunately (unless I've missed something) these involve listing all the instances of parent classes. I'm trying to avoid that. Indeed if I have to explicitly list all the instances I might as well write them the normal way so I'm not sure what the point of any trickery is.
Yes that approach does need declaring instances of `ShowPred` (or in general `XXXPred`), which doubles-up the instances for `Show` (or `XXX`). That approach is making a closed world: for every type `t`, return either `True` or `False` that it has an instance for `Show`. I'm not sure what you mean by "write them the normal way"? Just declaring `t` an instance of `Show` doesn't expose that in any way you can run type-level functions over it.
I also tried the associated types approach you suggested towards the end of your email previously. This works perfectly fine if you can edit the base class, but I can't edit say, "Applicative" or "Num".
Your O.P. talked about classes `A, B`, which I assumed you were writing fresh. What is your use case for wanting `EitherSatisfied (Applicative t) (Num t)` for the same `t`? (Those two classes want a `t` of different kind/arity, for one thing.) I did something like the following, but I ran into a problem:
... class Num t => MyNum t where type IsNum t instance Num t => MyNum t where type IsNum t = Satisfied
No that won't work (as others have pointed out). That `MyNum t` instance makes every type an instance of `MyNum`, so `(IsNum t)` always returns `Satisfied`. And it'll always compile because the instance constraint `Num t` is ignored for associated types. (Note that an Assoc type is just syntactic sugar for a type family, and you can't put constraints on TF equations.)
Any suggestions going forward from here?
I'm puzzled what it is you're trying to do. Your O.P. on the cafe talked about satisfying one or both of two constraints. Neither that nor your StackOverflow original question 'Reducing satisfied constraints to ordinary types' mentioned you want to use this for `Prelude` classes. Which specific classes? There is a ghc extension coming along soon which supports some limited froms of implications over types and instances. I'm not going to suggest it until I know what you're trying to do. I have a nervous feeling that you nurse incorrect expectations. This instance Num t => MyNum t where _does not_ say "if `t` is an instance of `Num`, it's thereby an instance of `MyNum`". It says "every `t` is an instance of `MyNum`; if I use some method from `MyNum` on some particular type `t0`, validate that also `Num t0`". There's a deep reason why Haskell works like that. See my added note to the SO answer. AntC

On Tue, May 8, 2018 at 11:17 AM, Anthony Clayden < anthony_clayden@clear.net.nz> wrote:
On Tue, 8 May 2018 at 12:20 AM, Clinton Mead
wrote: Firstly, you refer to https://wiki.haskell.org/GHC/AdvancedOverlap. Unfortunately (unless I've missed something) these involve listing all the instances of parent classes. I'm trying to avoid that. Indeed if I have to explicitly list all the instances I might as well write them the normal way so I'm not sure what the point of any trickery is.
Yes that approach does need declaring instances of `ShowPred` (or in general `XXXPred`), which doubles-up the instances for `Show` (or `XXX`). That approach is making a closed world: for every type `t`, return either `True` or `False` that it has an instance for `Show`.
I'm not sure what you mean by "write them the normal way"? Just declaring `t` an instance of `Show` doesn't expose that in any way you can run type-level functions over it.
By normal way as in if I need to list every instance as "ShowPred", I might as well just scrap "ShowPred" and just write them directly as instances of "Print". i.e. instance Print Int where print = show instance Print Char where print = show Seems not much more work than: instance ShowPred Int = HTrue instance ShowPred Bool = HTrue etc so why use "ShowPred" at all?
I'm puzzled what it is you're trying to do.
I'm trying to select instances based on whether constraints are fulfilled. For example: class Join m where join :: m (m a) -> m a instance Monad m => Join m where join x = x >>= id instance Comonad m => Join m where join = extract Obviously I can't do this, but consider: class Join' m (IsSatisfied m) (IsSatisfied m) => Join m where join :: m (m a) -> m a instance Join' m (IsSatisfied m) (IsSatisfied m) => Join m where join = join' instance Monad m => Join' m Satisfied t2 where join' x = x >>= id instance Comonad m => Join' m t1 Satisfied where join' = extract instance Comonad m => Join' m Satisfied Satisfied where join' = extract If I'm in a room, I can quite correctly assert things do exist if I can see them. But I can't assert things can't exist. Asserting things don't exist would violate the open world assumption, but only asserting when they do exist should not. "IsSatisfied" only needs to assert when the constraint is satisfied, it doesn't need to assert when it isn't, so I don't think it violates the open world assumption. Also GHC has this information to give an answer to IsSatisfied, it simply has to try to solve the constraint and if it succeeds reduce it to Satisfied, and if it doesn't it just does nothing. I just need a way to entice GHC to do that.

On Tue, 8 May 2018 at 2:00 PM, Clinton Mead
On Tue, May 8, 2018 at 11:17 AM, Anthony Clayden < anthony_clayden@clear.net.nz> wrote:
On Tue, 8 May 2018 at 12:20 AM, Clinton Mead
wrote: Firstly, you refer to https://wiki.haskell.org/GHC/AdvancedOverlap. Unfortunately (unless I've missed something) these involve listing all the instances of parent classes. I'm trying to avoid that. Indeed if I have to explicitly list all the instances I might as well write them the normal way so I'm not sure what the point of any trickery is.
Yes that approach does need declaring instances of `ShowPred` (or in general `XXXPred`), which doubles-up the instances for `Show` (or `XXX`). That approach is making a closed world: for every type `t`, return either `True` or `False` that it has an instance for `Show`.
I'm not sure what you mean by "write them the normal way"? Just declaring `t` an instance of `Show` doesn't expose that in any way you can run type-level functions over it.
By normal way as in if I need to list every instance as "ShowPred", I might as well just scrap "ShowPred" and just write them directly as instances of "Print". i.e.
No you haven't got it: there's a default/fallback instance for `Print` that applies if `ShowPred` comes out `False`. Also if you have `True/False` you can do Boolean logic over the result: type instance ShowPred [a] = ShowPred a -- implication type instance ShowPred (a, b) = And (ShowPred a) (ShowPred b) -- conjunction including either/or logic, which is where your O.P. started. (Although we seem to have come a long way from that.) So turning to your latest example ...
I'm puzzled what it is you're trying to do.
I'm trying to select instances based on whether constraints are fulfilled.
... consider:
class Join' m (IsSatisfied m) (IsSatisfied m) => Join m where join :: m (m a) -> m a
instance Join' m (IsSatisfied m) (IsSatisfied m) => Join m where join = join'
?? I think that context needs something like instance Join' m (IsMonad m) (IsComonad m) => Join m where ...
instance Monad m => Join' m Satisfied t2 where join' x = x >>= id
instance Comonad m => Join' m t1 Satisfied where join' = extract
instance Comonad m => Join' m Satisfied Satisfied where join' = extract
So if some `m0` is both a `Comonad` and a `Monad`, you want to prefer the `Comonad` method. If `m0` is a `Monad` but not a `Comonad`, use the `Monad` method. You've just invoked a closed world: you're relying on the compiler determining some `m0` is _not_ a `Comonad`. Technically: your instances for `Join'` overlap. So the compiler's inference selection must determine which is the more specific, given some particular `m0` with its `t1, t2` result from `IsSatisfied`. It can select head `Join' m Satisfied t2` only if it can prove `t2` is apart from `Satisfied`. ...
"IsSatisfied" only needs to assert when the constraint is satisfied, it doesn't need to assert when it isn't, ...
Yes it does for what you're asking to do. so I don't think it violates the open world assumption. Also GHC has this
information to give an answer to IsSatisfied, it simply has to try to solve the constraint and if it succeeds reduce it to Satisfied, and if it doesn't it just does nothing.
To the contrary: what you want it to do is select a different instance. AntC
participants (4)
-
Anthony Clayden
-
Brandon Allbery
-
Clinton Mead
-
David Kraeutmann