Re: Enum and bounded instances for (,) and Either

Hi all, Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist. The following is a reasonable implementation: instance (Bounded b, Enum a, Enum b) => Enum (a, b) where fromEnum (a, b) = (fromEnum (maxBound @b) + 1) * fromEnum a + fromEnum b toEnum n = let bound = fromEnum (maxBound @b) + 1 b = n `rem` bound a = n `div` bound in (toEnum a, toEnum b) And, while we're at it, might as well add canonical instances for Either: instance (Bounded a, Bounded b) => Bounded (Either a b) where minBound = Left minBound maxBound = Right maxBound instance (Bounded a, Enum a, Enum b) => Enum (Either a b) where toEnum i = let bound = fromEnum (maxBound @a) + 1 in case i < bound of True -> Left $ toEnum i False -> Right $ toEnum $ i - bound fromEnum (Left a) = fromEnum a fromEnum (Right b) = fromEnum b + fromEnum (maxBound @a) + 1 Are there any reasons these instances are missing from base? Cheers, Sandy

On Wed, May 12, 2021 at 10:52:56AM -0700, Sandy Maguire wrote:
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
I have also experienced this. Please see https://mail.haskell.org/pipermail/haskell-cafe/2018-June/129213.html I can't actually remember what the outcome of that thread was, but you may find it enlightening to read through it.

On Wed, 12 May 2021, Sandy Maguire wrote:
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
The following is a reasonable implementation:
This is certainly an instance that fulfills some laws, but do we really want it or is it more likely an accident if someone writes, say, succ (a,b)? We already allow "max (a,b) (c,d)", which can be pretty counterintuitive. I think the Ord instance for pairs is currently mostly used for Set and Map, but a separate class for this purpose would have been better.

I am +1 on these instances existing, but I just discovered via
quickcheck-classes that the given code does not uphold succ-pred or
pred-succ identity:
```
Enum: Succ Pred Identity *** Failed! Falsified (after 2 tests):
Description: succ (pred x) = x
a = (0,1)
succ (pred x) = (-1,1)
Enum: Pred Succ Identity *** Failed! Falsified (after 1 test):
Description: pred (succ x) = x
a = (0,0)
pred (succ x) = (1,0)
*** Failed! Falsified (after 2 tests)..
Enum: Succ Pred Identity *** Failed! Falsified (after 2 tests):
Description: succ (pred x) = x
a = Left 0
succ (pred x) = Right (-9223372036854775808)
Enum: Pred Succ Identity *** Failed! Falsified (after 1 test):
Description: pred (succ x) = x
a = Left 0
pred (succ x) = Right (-9223372036854775808)
```
Perhaps this is indicative of something wrong with the proposed instance?
Thanks
On Wed, May 12, 2021 at 11:01 AM Henning Thielemann
On Wed, 12 May 2021, Sandy Maguire wrote:
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
The following is a reasonable implementation:
This is certainly an instance that fulfills some laws, but do we really want it or is it more likely an accident if someone writes, say, succ (a,b)?
We already allow "max (a,b) (c,d)", which can be pretty counterintuitive. I think the Ord instance for pairs is currently mostly used for Set and Map, but a separate class for this purpose would have been better. _______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

Note that it is probably just missing some `error` calls in the right place.
On Wed, May 12, 2021, 13:27 chessai
I am +1 on these instances existing, but I just discovered via quickcheck-classes that the given code does not uphold succ-pred or pred-succ identity:
``` Enum: Succ Pred Identity *** Failed! Falsified (after 2 tests): Description: succ (pred x) = x a = (0,1) succ (pred x) = (-1,1) Enum: Pred Succ Identity *** Failed! Falsified (after 1 test): Description: pred (succ x) = x a = (0,0) pred (succ x) = (1,0) *** Failed! Falsified (after 2 tests).. Enum: Succ Pred Identity *** Failed! Falsified (after 2 tests): Description: succ (pred x) = x a = Left 0 succ (pred x) = Right (-9223372036854775808) Enum: Pred Succ Identity *** Failed! Falsified (after 1 test): Description: pred (succ x) = x a = Left 0 pred (succ x) = Right (-9223372036854775808) ```
Perhaps this is indicative of something wrong with the proposed instance?
Thanks
On Wed, May 12, 2021 at 11:01 AM Henning Thielemann
wrote: On Wed, 12 May 2021, Sandy Maguire wrote:
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
The following is a reasonable implementation:
This is certainly an instance that fulfills some laws, but do we really want it or is it more likely an accident if someone writes, say, succ (a,b)?
We already allow "max (a,b) (c,d)", which can be pretty counterintuitive. I think the Ord instance for pairs is currently mostly used for Set and Map, but a separate class for this purpose would have been better. _______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

On May 12, 2021, at 4:35 PM, chessai
wrote: Note that it is probably just missing some `error` calls in the right place.
Well, fromEnum for such instances is necessarily fragile, because: * The cardinality of the of the product set will often exceed the range of Int values, as e.g. with (Int, a) for any `a` with two or more elements. * The pair instance assumes that Bounded Enums have a minBound of zero, this is not true for e.g. Int8. The indices of the *inner*, less significant tuple terms have to be shifted to start at zero (possibly even using a larger integral result type than Int). innerToEnum x = toEnum (x :: t) - toEnum (minBound @t). Getting this right takes care, and it is IMHO likely to cause more issues that it would solve. Users who really want this can employ suitable newtype wrappers. -- Viktor.

Nothing indicates that minBound is >= 0. So if you want to deal with a modulus properly you need to deal with a modulus of something like maxBound - minBound + 1 (after appropriately upcasting intermediate results to a large enough type to contain that range). You also run into range issues pretty early on, and then scenarios like the instances for Float that are even wonkier. If toEnum/fromEnum went to a larger type (Integer or Natural) and we didn't have the wonkier Float instances and the like this would be a much easier sell. As it is I find myself rather uncomfortable with the shakiness of the foundations this thing rests upon. Sent from my iPhone
On May 12, 2021, at 10:53 AM, Sandy Maguire
wrote: Hi all,
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
The following is a reasonable implementation:
instance (Bounded b, Enum a, Enum b) => Enum (a, b) where fromEnum (a, b) = (fromEnum (maxBound @b) + 1) * fromEnum a + fromEnum b toEnum n = let bound = fromEnum (maxBound @b) + 1 b = n `rem` bound a = n `div` bound in (toEnum a, toEnum b)
And, while we're at it, might as well add canonical instances for Either:
instance (Bounded a, Bounded b) => Bounded (Either a b) where minBound = Left minBound maxBound = Right maxBound
instance (Bounded a, Enum a, Enum b) => Enum (Either a b) where toEnum i = let bound = fromEnum (maxBound @a) + 1 in case i < bound of True -> Left $ toEnum i False -> Right $ toEnum $ i - bound fromEnum (Left a) = fromEnum a fromEnum (Right b) = fromEnum b + fromEnum (maxBound @a) + 1
Are there any reasons these instances are missing from base?
Cheers, Sandy
_______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

isnt there also the issue that while we good provide newtypes for various
choices of product of enum instance => enum of products, there literally
quite easily 3 valid instances we could concoct (and many more besides)
"row by row"
"column by column"
"diagonalization"
etc?
that is: each of these is just as valid?
On Wed, May 12, 2021 at 5:46 PM Edward Kmett
Nothing indicates that minBound is >= 0.
So if you want to deal with a modulus properly you need to deal with a modulus of something like maxBound - minBound + 1 (after appropriately upcasting intermediate results to a large enough type to contain that range).
You also run into range issues pretty early on, and then scenarios like the instances for Float that are even wonkier.
If toEnum/fromEnum went to a larger type (Integer or Natural) and we didn't have the wonkier Float instances and the like this would be a much easier sell. As it is I find myself rather uncomfortable with the shakiness of the foundations this thing rests upon.
Sent from my iPhone
On May 12, 2021, at 10:53 AM, Sandy Maguire
wrote: Hi all,
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
The following is a reasonable implementation:
instance (Bounded b, Enum a, Enum b) => Enum (a, b) where fromEnum (a, b) = (fromEnum (maxBound @b) + 1) * fromEnum a + fromEnum b toEnum n = let bound = fromEnum (maxBound @b) + 1 b = n `rem` bound a = n `div` bound in (toEnum a, toEnum b)
And, while we're at it, might as well add canonical instances for Either:
instance (Bounded a, Bounded b) => Bounded (Either a b) where minBound = Left minBound maxBound = Right maxBound
instance (Bounded a, Enum a, Enum b) => Enum (Either a b) where toEnum i = let bound = fromEnum (maxBound @a) + 1 in case i < bound of True -> Left $ toEnum i False -> Right $ toEnum $ i - bound fromEnum (Left a) = fromEnum a fromEnum (Right b) = fromEnum b + fromEnum (maxBound @a) + 1
Are there any reasons these instances are missing from base?
Cheers, Sandy
_______________________________________________
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

*while we could
Perhaps we should provide these flavors via new types and thus folks could
still benefit from these instances by way of deriving via ?
On Wed, May 12, 2021 at 6:38 PM Carter Schonwald
isnt there also the issue that while we good provide newtypes for various choices of product of enum instance => enum of products, there literally quite easily 3 valid instances we could concoct (and many more besides)
"row by row" "column by column" "diagonalization" etc? that is: each of these is just as valid?
On Wed, May 12, 2021 at 5:46 PM Edward Kmett
wrote: Nothing indicates that minBound is >= 0.
So if you want to deal with a modulus properly you need to deal with a modulus of something like maxBound - minBound + 1 (after appropriately upcasting intermediate results to a large enough type to contain that range).
You also run into range issues pretty early on, and then scenarios like the instances for Float that are even wonkier.
If toEnum/fromEnum went to a larger type (Integer or Natural) and we didn't have the wonkier Float instances and the like this would be a much easier sell. As it is I find myself rather uncomfortable with the shakiness of the foundations this thing rests upon.
Sent from my iPhone
On May 12, 2021, at 10:53 AM, Sandy Maguire
wrote: Hi all,
Found myself puzzled the other day when I wanted an (Enum a, Enum b) => Enum (a, b) instance, and was distraught that it didn't exist.
The following is a reasonable implementation:
instance (Bounded b, Enum a, Enum b) => Enum (a, b) where fromEnum (a, b) = (fromEnum (maxBound @b) + 1) * fromEnum a + fromEnum b toEnum n = let bound = fromEnum (maxBound @b) + 1 b = n `rem` bound a = n `div` bound in (toEnum a, toEnum b)
And, while we're at it, might as well add canonical instances for Either:
instance (Bounded a, Bounded b) => Bounded (Either a b) where minBound = Left minBound maxBound = Right maxBound
instance (Bounded a, Enum a, Enum b) => Enum (Either a b) where toEnum i = let bound = fromEnum (maxBound @a) + 1 in case i < bound of True -> Left $ toEnum i False -> Right $ toEnum $ i - bound fromEnum (Left a) = fromEnum a fromEnum (Right b) = fromEnum b + fromEnum (maxBound @a) + 1
Are there any reasons these instances are missing from base?
Cheers, Sandy
_______________________________________________
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
participants (7)
-
Carter Schonwald
-
chessai
-
Edward Kmett
-
Henning Thielemann
-
Sandy Maguire
-
Tom Ellis
-
Viktor Dukhovni