Optional "Default" instances of classes in certain subcases (but too broad)

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

Optional "Default" instances of classes in certain subcases (but too broad)

Juan Casanova
Hello again,

Before my question, I notice I am using this list quite a bit. I hope  
I am no abusing or misusing it by doing so, though. Sometimes I wonder  
what's the line between what I should ask here and what I should ask  
in StackOverflow, if that line even exists. If anyone feels I may be  
abusing or misusing, please let me know (in public or in private).

Very simple question: Would it make sense, or does it already exist, a  
way to implement *optional* default instances of classes that can be  
used more directly than currently.

I am *not* talking about providing a default implementation of a class  
when declaring it. I know you can do this.

I am talking about when you know you can provide a generic instance of  
a class for a wide range of situations *which depends on some  
constraints*. For example:

instance Bifunctor f => Functor (f a) where
     fmap = bimap id

Of course, this is a terrible idea, because the constraints are not  
checked when verifying overlap and the like, and so this is very  
likely to break your program. What I tend to do nowadays is to add a  
function:

fmapFromBiMap :: Bifunctor f => (b -> c) -> (f a b) -> (f a c)
fmapFromBiMap = bimap id

and then use it each time (the following is obviously a silly example):

instance Functor (Either a) where
     fmap = fmapFromBiMap

The only problem I see with this is that I need to remember (specially  
with my own type classes): 1. That I provided this "default"  
implementation. 2. Its name. I know these are fairly small things to  
complain about, but once again when you do it 100 times it becomes  
annoying, specially for a person who absolutely does not like doing  
things that I know would be able to explain how to automate.

So, what I would expect is for it to be something like declaring the  
default instance but only use it for the types that explicitly ask for  
it. E.g.:

optional instance Bifunctor f => Functor (f a) where
     fmap = bimap id

instance Functor (Either a) using optional

The main important point here is that I expect this to check the  
constraints in compile time for the class that I am giving (and if it  
contains type variables, only use those that are guaranteed to be  
complied, of course). This means that if I did:

instance Functor (f a) using optional

I *do not* expect it to be okay with it and automatically add the  
constraint Bifunctor f => to the instance. I expect it to give me a  
compile error: "No matching optional instance found".

Of course, there could be several optional instances that overlap when  
using optional. But then, GHC would give me the error indicating what  
the options are *and I would know that it's because I put optional*  
and I could just replace it with the specific one to use. Maybe by  
giving them names?

optional instance fmapFromBiMap Bifunctor f => Functor (f a) where
     fmap = bimap id

instance Functor (Either a) using optional -- If this does not work  
because it overlaps with other optionals, then GHC tells me, indicates  
the options, and I replace with:

instance Functor (Either a) using fmapFromBiMap

This avoids the problem of defining the instance in general (that it  
will overlap with basically anything), should be easy to type check  
and prevents having to keep an index in your mind about useful  
functions that you implemented or didn't.

I wonder if the "deriving" family of functionalities have anything  
like this, but my search has not been fruitful. Maybe using Generics?

Maybe I just need good autocomplete so that I can find the function  
easily... it still feels like this would make sense.

Juan.

--
The University of Edinburgh is a charitable body, registered in
Scotland, with registration number SC005336.


_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Optional "Default" instances of classes in certain subcases (but too broad)

Alexis King
Hello,

The recent DerivingVia language extension is, in many ways, designed to accommodate the use cases you describe. Instead of defining “optional” instances, you attach instances to a relevant newtype, then use that newtype with DerivingVia to reuse instances.

So, for example, you could define a WrappedBifunctor newtype:

newtype WrappedBifunctor f a b = WrappedBifunctor { unWrappedBifunctor :: f a b }
  deriving newtype (Bifunctor)

…then declare the following instance:

instance Bifunctor f => Functor (WrappedBifunctor f a) where
  fmap = bimap id

Then if you have a type with an explicit Bifunctor instance, you can write:

data F a b = ...
  deriving (Functor) via WrappedBifunctor F

…or, with StandaloneDeriving:

deriving via (WrappedBifunctor F) instance Functor (F a)

Does that accommodate the use cases you’re looking for?

Alexis

> On Oct 4, 2019, at 16:08, Juan Casanova <[hidden email]> wrote:
>
> Hello again,
>
> Before my question, I notice I am using this list quite a bit. I hope I am no abusing or misusing it by doing so, though. Sometimes I wonder what's the line between what I should ask here and what I should ask in StackOverflow, if that line even exists. If anyone feels I may be abusing or misusing, please let me know (in public or in private).
>
> Very simple question: Would it make sense, or does it already exist, a way to implement *optional* default instances of classes that can be used more directly than currently.
>
> I am *not* talking about providing a default implementation of a class when declaring it. I know you can do this.
>
> I am talking about when you know you can provide a generic instance of a class for a wide range of situations *which depends on some constraints*. For example:
>
> instance Bifunctor f => Functor (f a) where
>    fmap = bimap id
>
> Of course, this is a terrible idea, because the constraints are not checked when verifying overlap and the like, and so this is very likely to break your program. What I tend to do nowadays is to add a function:
>
> fmapFromBiMap :: Bifunctor f => (b -> c) -> (f a b) -> (f a c)
> fmapFromBiMap = bimap id
>
> and then use it each time (the following is obviously a silly example):
>
> instance Functor (Either a) where
>    fmap = fmapFromBiMap
>
> The only problem I see with this is that I need to remember (specially with my own type classes): 1. That I provided this "default" implementation. 2. Its name. I know these are fairly small things to complain about, but once again when you do it 100 times it becomes annoying, specially for a person who absolutely does not like doing things that I know would be able to explain how to automate.
>
> So, what I would expect is for it to be something like declaring the default instance but only use it for the types that explicitly ask for it. E.g.:
>
> optional instance Bifunctor f => Functor (f a) where
>    fmap = bimap id
>
> instance Functor (Either a) using optional
>
> The main important point here is that I expect this to check the constraints in compile time for the class that I am giving (and if it contains type variables, only use those that are guaranteed to be complied, of course). This means that if I did:
>
> instance Functor (f a) using optional
>
> I *do not* expect it to be okay with it and automatically add the constraint Bifunctor f => to the instance. I expect it to give me a compile error: "No matching optional instance found".
>
> Of course, there could be several optional instances that overlap when using optional. But then, GHC would give me the error indicating what the options are *and I would know that it's because I put optional* and I could just replace it with the specific one to use. Maybe by giving them names?
>
> optional instance fmapFromBiMap Bifunctor f => Functor (f a) where
>    fmap = bimap id
>
> instance Functor (Either a) using optional -- If this does not work because it overlaps with other optionals, then GHC tells me, indicates the options, and I replace with:
>
> instance Functor (Either a) using fmapFromBiMap
>
> This avoids the problem of defining the instance in general (that it will overlap with basically anything), should be easy to type check and prevents having to keep an index in your mind about useful functions that you implemented or didn't.
>
> I wonder if the "deriving" family of functionalities have anything like this, but my search has not been fruitful. Maybe using Generics?
>
> Maybe I just need good autocomplete so that I can find the function easily... it still feels like this would make sense.
>
> Juan.
>
> --
> The University of Edinburgh is a charitable body, registered in
> Scotland, with registration number SC005336.
>
>
> _______________________________________________
> 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.

_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Optional "Default" instances of classes in certain subcases (but too broad)

Juan Casanova
> Does that accommodate the use cases you’re looking for?

It probably does, to be honest. I need to try it, but from your  
example I gather that the newtype is only defined so that the deriving  
can be used, but I do not ever need to explicitly use the newtype that  
I declared in order to be able to derive the functor instance. It is  
essentially what I said, except it uses the newtype as a "carrier" for  
the optional instance.

Thanks. If I find out any use case where it's not enough I'll come  
back here, but it does look like it's enough.

Juan.


--
The University of Edinburgh is a charitable body, registered in
Scotland, with registration number SC005336.


_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Optional "Default" instances of classes in certain subcases (but too broad)

Juan Casanova
In reply to this post by Alexis King
A follow-up from this discussion.

It would seem that this is not currently usable for multi-parameter  
type classes when we want to derive more than the last parameter. Is  
this right?

I have found this paper that seems to say so, and leave it as an open problem:

https://www.kosmikus.org/DerivingVia/deriving-via-paper.pdf

As I see it, it is only a syntax issue, whereby we need a way of  
telling GHC which arguments should be derived. Are there any other  
reasons why this is not implemented yet?

I don't think designing a syntax to specify this should be that  
difficult. A quick idea that comes to mind is indicating in the "via"  
clause a tuple of instances so that GHC knows which one to use for  
each of the last parameters, for example:

class Class1 a b c | a b -> c where
   fun1 :: a -> b -> c

newtype NT1 = NT1 Int
newtype NT2 = NT2 Int

-- Two actually different instances that could be applicable.
instance Class1 NT1 NT1 NT1 where
   fun1 (NT1 x) (NT1 y) = NT1 (x + y)

instance Class1 NT1 NT2 NT2 where
   fun1 (NT1 x) (NT2 y) = NT2 (x - y)

-- The following instances are, of course, incompatible between them;  
but each of them should be valid on their own, doing different things.

-- The following would only derive the last parameter
deriving via (NT1) instance Class1 NT1 NT1 Int

-- The following would only derive the last parameter, but using NT2 instead
deriving via (NT2) instance Class1 NT1 NT1 Int

-- The following would derive the last two parameters, using the first  
instance above.
deriving via (NT1,NT1) instance Class1 NT1 Int Int

-- The following would derive the last two parameters, using the  
second instance above.
deriving via (NT2,NT2) instance Class1 NT1 Int Int

-- The following would derive all three parameters, using the first  
instance above.
deriving via (NT1,NT1,NT1) instance Class1 Int Int Int

-- The following would derive all three parameters, using the second  
instance above.
deriving via (NT1,NT2,NT2) instance Class1 Int Int Int

-- The following would not work, because there is no instance for  
Class1 NT2 NT2 NT2.
deriving via (NT2,NT2,NT2) instance Class1 Int Int Int

Any reason why this should not be posted as a ticket to GHC?

Juan.

Quoting Alexis King <[hidden email]> on Fri, 4 Oct 2019 20:21:22 -0500:

> Hello,
>
> The recent DerivingVia language extension is, in many ways, designed  
> to accommodate the use cases you describe. Instead of defining  
> “optional” instances, you attach instances to a relevant newtype,  
> then use that newtype with DerivingVia to reuse instances.
>
> So, for example, you could define a WrappedBifunctor newtype:
>
> newtype WrappedBifunctor f a b = WrappedBifunctor {  
> unWrappedBifunctor :: f a b }
>   deriving newtype (Bifunctor)
>
> …then declare the following instance:
>
> instance Bifunctor f => Functor (WrappedBifunctor f a) where
>   fmap = bimap id
>
> Then if you have a type with an explicit Bifunctor instance, you can write:
>
> data F a b = ...
>   deriving (Functor) via WrappedBifunctor F
>
> …or, with StandaloneDeriving:
>
> deriving via (WrappedBifunctor F) instance Functor (F a)
>
> Does that accommodate the use cases you’re looking for?
>
> Alexis
>
>> On Oct 4, 2019, at 16:08, Juan Casanova <[hidden email]> wrote:
>>
>> Hello again,
>>
>> Before my question, I notice I am using this list quite a bit. I  
>> hope I am no abusing or misusing it by doing so, though. Sometimes  
>> I wonder what's the line between what I should ask here and what I  
>> should ask in StackOverflow, if that line even exists. If anyone  
>> feels I may be abusing or misusing, please let me know (in public  
>> or in private).
>>
>> Very simple question: Would it make sense, or does it already  
>> exist, a way to implement *optional* default instances of classes  
>> that can be used more directly than currently.
>>
>> I am *not* talking about providing a default implementation of a  
>> class when declaring it. I know you can do this.
>>
>> I am talking about when you know you can provide a generic instance  
>> of a class for a wide range of situations *which depends on some  
>> constraints*. For example:
>>
>> instance Bifunctor f => Functor (f a) where
>>    fmap = bimap id
>>
>> Of course, this is a terrible idea, because the constraints are not  
>> checked when verifying overlap and the like, and so this is very  
>> likely to break your program. What I tend to do nowadays is to add  
>> a function:
>>
>> fmapFromBiMap :: Bifunctor f => (b -> c) -> (f a b) -> (f a c)
>> fmapFromBiMap = bimap id
>>
>> and then use it each time (the following is obviously a silly example):
>>
>> instance Functor (Either a) where
>>    fmap = fmapFromBiMap
>>
>> The only problem I see with this is that I need to remember  
>> (specially with my own type classes): 1. That I provided this  
>> "default" implementation. 2. Its name. I know these are fairly  
>> small things to complain about, but once again when you do it 100  
>> times it becomes annoying, specially for a person who absolutely  
>> does not like doing things that I know would be able to explain how  
>> to automate.
>>
>> So, what I would expect is for it to be something like declaring  
>> the default instance but only use it for the types that explicitly  
>> ask for it. E.g.:
>>
>> optional instance Bifunctor f => Functor (f a) where
>>    fmap = bimap id
>>
>> instance Functor (Either a) using optional
>>
>> The main important point here is that I expect this to check the  
>> constraints in compile time for the class that I am giving (and if  
>> it contains type variables, only use those that are guaranteed to  
>> be complied, of course). This means that if I did:
>>
>> instance Functor (f a) using optional
>>
>> I *do not* expect it to be okay with it and automatically add the  
>> constraint Bifunctor f => to the instance. I expect it to give me a  
>> compile error: "No matching optional instance found".
>>
>> Of course, there could be several optional instances that overlap  
>> when using optional. But then, GHC would give me the error  
>> indicating what the options are *and I would know that it's because  
>> I put optional* and I could just replace it with the specific one  
>> to use. Maybe by giving them names?
>>
>> optional instance fmapFromBiMap Bifunctor f => Functor (f a) where
>>    fmap = bimap id
>>
>> instance Functor (Either a) using optional -- If this does not work  
>> because it overlaps with other optionals, then GHC tells me,  
>> indicates the options, and I replace with:
>>
>> instance Functor (Either a) using fmapFromBiMap
>>
>> This avoids the problem of defining the instance in general (that  
>> it will overlap with basically anything), should be easy to type  
>> check and prevents having to keep an index in your mind about  
>> useful functions that you implemented or didn't.
>>
>> I wonder if the "deriving" family of functionalities have anything  
>> like this, but my search has not been fruitful. Maybe using Generics?
>>
>> Maybe I just need good autocomplete so that I can find the function  
>> easily... it still feels like this would make sense.
>>
>> Juan.
>>
>> --
>> The University of Edinburgh is a charitable body, registered in
>> Scotland, with registration number SC005336.
>>
>>
>> _______________________________________________
>> 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.
>
>
>



--
The University of Edinburgh is a charitable body, registered in
Scotland, with registration number SC005336.


_______________________________________________
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.
Reply | Threaded
Open this post in threaded view
|

Re: Optional "Default" instances of classes in certain subcases (but too broad)

Juan Casanova
Correction to my last email:

-- The following would not work because there is no instance Class1  
NT1 NT1 NT2
deriving via (NT2) instance Class1 NT1 NT1 Int

-- But this would work
deriving via (NT2) instance Class1 NT1 NT2 Int


--
The University of Edinburgh is a charitable body, registered in
Scotland, with registration number SC005336.


_______________________________________________
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.