
I have no intuition about what Enum should do about void datatypes;
it's too broken. Bounded has one sensible default (minBound and
maxBound should produce an error message naming the type and explain
that it has no values). Ix gets pretty weird here. I would think
range = const []
index _ x = case x of
inRange _ _ = False OR inRange _ x = case x of {} (is it sensible
to ask if _|_ is in an empty range?)
rangeSize = const 0
On Mon, Jan 16, 2017 at 3:37 PM, Ryan Scott
Thanks for the clarification. I definitely agree that the current error-based behavior for classes where T occurs in a negative position is suboptimal. If I understand your position correctly, then you advocate determining what the derived code for methods in which T occurs negatively on a case-by-case basis? How about I just list all of the stock derivable classes, give example instances, and see if you agree with them?
data Empty a deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable, Lift, Generic, Data) ===> instance Eq (Empty a) where _ == _ = True instance Ord (Empty a) where compare _ _ = EQ instance Read (Empty a) where readPrec = parens pfail instance Show (Empty a) where showsPrec _ = absurd instance Functor Empty where fmap _ = absurd instance Foldable Empty where foldr _ z _ = z foldMap _ _ = mempty instance Traversable Empty where traverse _ = absurd instance Lift (Empty a) where lift = absurd instance Generic (Empty a) where from = absurd to = absurd instance Data (Empty a) where gfoldl _ = absurd gunfold _ _ c = constrIndex c of {} toConstr = absurd dataTypeOf = mkDataType "Empty" []
absurd :: Empty a -> b absurd x = case x of {}
Note I didn't list Bounded, Enum, or Ix because GHC currently forbids you from deriving instances of those for empty datatypes, even with -XStandaloneDeriving on. Do you think we should revisit this?
Ryan S.
On Mon, Jan 16, 2017 at 3:09 PM, Edward Kmett
wrote: By current behavior, I was referring to the behavior of Void, not deriving. I think the current deriving behavior on this front is suboptimal by the reasoning given in my previous post.
-Edward
On Mon, Jan 16, 2017 at 1:51 PM, Ryan Scott
wrote: I think I mostly agree with everything you've said, but I'm not sure I understood all the details, so let me try to recap:
In the case it doesn't occur at all or occurs only positively, er... we don't care. We're not case analyzing it =)
Indeed there's only one stock class you can derive where the type occurs only positively, and that's Read, which already has nice behavior for empty datatypes:
data Empty deriving instance Read Empty ====> instance Read Empty where readPrec = parens pfail
So indeed, this an easy case.
In the case of it occurring in both positive and negative position we have the option to 'pass it through' to exchange the bottom. In the case of changing the behavior of Functor and the like we're not actually changing the definedness of the result, we're merely choosing between "equivalent" bottoms. This is a rather exceptional case, but the ability to preserve placed bottoms in a structure in case they have meaning to the user seems quite valuable.
To be clear, this corresponds to David's proposal to use -XEmptyCase, right?
In the case of negative position only, the current behavior is more defined than the stricter behavior Ryan proposes. I personally very much favor keeping Eq, Ord, etc. as defined as possible for Void, V1 and the like with the existing behavior. 'a' occurs in negative position only for these cases.
What do you mean by "the current behavior" here? For instance, if you had:
data Empty deriving instance Eq Empty
Are you defining "the current behavior" to mean this? That is, what GHC currently spits out today:
instance Eq Empty where (==) = error "Void =="
Or by "current behavior", do you mean the (manually written) instance that Void has:
instance Eq Void where _ == _ = True
These two instances are quite different, after all.
Similarly the existing practice of not doing wasted work and producing more defined results, also seems valuable. Aiming for "consistency" here seems to be pursuing a goal that doesn't actually help anyone and just makes stuff less defined.
Fully agree.
Ryan S.
On Mon, Jan 16, 2017 at 12:55 PM, Edward Kmett
wrote: There are really four cases to consider for deriving purposes.
Some type T occurs positively, negative, not at all, or in both.
* In the case it doesn't occur at all or occurs only positively, er... we don't care. We're not case analyzing it =)
* In the case of negative position only, the current behavior is more defined than the stricter behavior Ryan proposes. I personally very much favor keeping Eq, Ord, etc. as defined as possible for Void, V1 and the like with the existing behavior. 'a' occurs in negative position only for these cases.
* In the case of it occurring in both positive and negative position we have the option to 'pass it through' to exchange the bottom. In the case of changing the behavior of Functor and the like we're not actually changing the definedness of the result, we're merely choosing between "equivalent" bottoms. This is a rather exceptional case, but the ability to preserve placed bottoms in a structure in case they have meaning to the user seems quite valuable.
Similarly the existing practice of not doing wasted work and producing more defined results, also seems valuable. Aiming for "consistency" here seems to be pursuing a goal that doesn't actually help anyone and just makes stuff less defined.
-Edward
On Mon, Jan 16, 2017 at 9:25 AM, Ryan Scott
wrote: To be clear, if you have:
data V a deriving Functor
David is proposing in #13117 [1] that the derived instance use -XEmptyCase instead of the current error-based implementation:
instance Functor V where fmap _ x = case x of {}
This seems fine and well, but given that #13117 is basically a continuation of another discussion in #7401 [2], I feel like if we're going to tackle the issue of derived instances for empty data declaration, we should solve it for all stock classes.
In particular, there are two thorny issues to consider here. What if we have this instead?
data V2 deriving Eq
What instance should this produce? Reid Barton proposed in #10577 [3] that it should be:
instance Eq V2 where a == _ = case a of {}
But we have a choice here, since (==) has multiple arguments! The definition could also conceivably be:
instance Eq V2 where _ == b = case b of {}
Is there a uniform policy we can decide for functions with multiple arguments like this? In fmap, it's unambiguous since there's only one argument of type f a.
Another issue to consider is that if we adopted this convention for derived Eq instances for empty datatypes, we'd actually be going against the convention set for Data.Void. As noted in [4], this is the current Eq instance for Void:
instance Eq Void where _ == _ = True
I'm not proposing that we change this definition, since there are many good reasons to have it this way (see the thread in [5] for Edward Kmett's convincing argument in favor of the current Eq Void instance). Rather, I'm asking if we would be OK with having this discrepancy--that is, deriving Eq for your own Void2 type would produce a different instance. Personally, I'd be fine with it, but I think we should ask for the community's input as well.
So sorry to hijack this thread, David, but we really should answer these two questions as well:
1. What do we do in ambiguous cases like derived (==) implementations for empty datatypes? 2. Are we fine with derived instances for empty datatypes sometimes being different than the corresponding instances for Data.Void?
Ryan S. ----- [1] https://ghc.haskell.org/trac/ghc/ticket/13117 [2] https://ghc.haskell.org/trac/ghc/ticket/7401#comment:46 [3] https://ghc.haskell.org/trac/ghc/ticket/10577 [4] https://mail.haskell.org/pipermail/libraries/2015-July/025959.html [5] https://mail.haskell.org/pipermail/libraries/2015-July/025965.html _______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries