Min & Max without Bounded

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

Min & Max without Bounded

Jon Purdy
The ‘Min’ and ‘Max’ newtypes from ‘Data.Semigroup’ require a ‘Bounded’ constraint for their ‘Monoid’ instances, because they wrap a value of type ‘a’ rather than ‘Maybe a’. The latter is something I’ve needed a few times lately for types that cannot be bounded (or just don’t want to be), and it seems base-worthy to me.

So if it hasn’t been proposed and dismissed already (or if it has been proposed, but just got backburnered), I’d like to suggest adding the following new types, and at least the following instances, to either ‘Data.Semigroup’ or ‘Data.Monoid’:

newtype Minimum a = Minimum { getMinimum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Down (Maybe (Down (Max a))))

newtype Maximum a = Maximum { getMaximum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Maybe (Max a))

These instances could be specified explicitly instead of with ‘DerivingVia’, but the essential notion here is that ‘Maximum’ uses its empty value ‘Nothing’ as a “negative infinity”, less than all other ‘Just’ values, while ‘Minimum’ reverses that to use ‘Nothing’ as the corresponding “positive infinity”.

I assume these would also want more or less the same array of instances as for the existing ‘Min’ and ‘Max’, and there is the usual opportunity to bikeshed about the naming and how these instances ought to relate to one another. I figure that at least the ‘Ord’ instance should match the ordering that the ‘Semigroup’ instance uses, to avoid some potential for confusion/bugs.

As for use cases, I tend to reach for these things either in configuration records with many fields that I’m combining by semigroup append (Max, Any, set union, and so on) or when I have a possibly-empty container and don’t want to write e.g. ‘maximum (0 : xs)’ to avoid partiality. For example, ‘foldMap (Minimum . Just &&& Maximum . Just)’ computes the minimum and maximum in a single pass, returning Nothing for a null input.

Any thoughts, critiques, questions, rebuttals, edge cases, use cases, &c. are most welcome!


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Min & Max without Bounded

Hécate

Hi Jon,

While I do not have a particularly strong opinion on the changes you're suggesting, I invite you to read the source of this module, which may provide some context for the current situation:

https://hackage.haskell.org/package/base-4.14.0.0/docs/src/Data.Functor.Utils.html#%23

Cheers!

Le 10/11/2020 à 22:19, Jon Purdy a écrit :
The ‘Min’ and ‘Max’ newtypes from ‘Data.Semigroup’ require a ‘Bounded’ constraint for their ‘Monoid’ instances, because they wrap a value of type ‘a’ rather than ‘Maybe a’. The latter is something I’ve needed a few times lately for types that cannot be bounded (or just don’t want to be), and it seems base-worthy to me.

So if it hasn’t been proposed and dismissed already (or if it has been proposed, but just got backburnered), I’d like to suggest adding the following new types, and at least the following instances, to either ‘Data.Semigroup’ or ‘Data.Monoid’:

newtype Minimum a = Minimum { getMinimum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Down (Maybe (Down (Max a))))

newtype Maximum a = Maximum { getMaximum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Maybe (Max a))

These instances could be specified explicitly instead of with ‘DerivingVia’, but the essential notion here is that ‘Maximum’ uses its empty value ‘Nothing’ as a “negative infinity”, less than all other ‘Just’ values, while ‘Minimum’ reverses that to use ‘Nothing’ as the corresponding “positive infinity”.

I assume these would also want more or less the same array of instances as for the existing ‘Min’ and ‘Max’, and there is the usual opportunity to bikeshed about the naming and how these instances ought to relate to one another. I figure that at least the ‘Ord’ instance should match the ordering that the ‘Semigroup’ instance uses, to avoid some potential for confusion/bugs.

As for use cases, I tend to reach for these things either in configuration records with many fields that I’m combining by semigroup append (Max, Any, set union, and so on) or when I have a possibly-empty container and don’t want to write e.g. ‘maximum (0 : xs)’ to avoid partiality. For example, ‘foldMap (Minimum . Just &&& Maximum . Just)’ computes the minimum and maximum in a single pass, returning Nothing for a null input.

Any thoughts, critiques, questions, rebuttals, edge cases, use cases, &c. are most welcome!


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
-- 
Hécate ✨
🐦: @TechnoEmpress
IRC: Uniaika
WWW: https://glitchbra.in
RUN: BSD

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Min & Max without Bounded

Oleg Grenrus
In reply to this post by Jon Purdy

Aren't Min and Max sufficient by themselves:

>>> foldMap (Just . Min &&& Just . Max) [1..10 :: Integer]
(Just (Min {getMin = 1}),Just (Max {getMax = 10}))

- Oleg

On 10.11.2020 23.19, Jon Purdy wrote:
The ‘Min’ and ‘Max’ newtypes from ‘Data.Semigroup’ require a ‘Bounded’ constraint for their ‘Monoid’ instances, because they wrap a value of type ‘a’ rather than ‘Maybe a’. The latter is something I’ve needed a few times lately for types that cannot be bounded (or just don’t want to be), and it seems base-worthy to me.

So if it hasn’t been proposed and dismissed already (or if it has been proposed, but just got backburnered), I’d like to suggest adding the following new types, and at least the following instances, to either ‘Data.Semigroup’ or ‘Data.Monoid’:

newtype Minimum a = Minimum { getMinimum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Down (Maybe (Down (Max a))))

newtype Maximum a = Maximum { getMaximum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Maybe (Max a))

These instances could be specified explicitly instead of with ‘DerivingVia’, but the essential notion here is that ‘Maximum’ uses its empty value ‘Nothing’ as a “negative infinity”, less than all other ‘Just’ values, while ‘Minimum’ reverses that to use ‘Nothing’ as the corresponding “positive infinity”.

I assume these would also want more or less the same array of instances as for the existing ‘Min’ and ‘Max’, and there is the usual opportunity to bikeshed about the naming and how these instances ought to relate to one another. I figure that at least the ‘Ord’ instance should match the ordering that the ‘Semigroup’ instance uses, to avoid some potential for confusion/bugs.

As for use cases, I tend to reach for these things either in configuration records with many fields that I’m combining by semigroup append (Max, Any, set union, and so on) or when I have a possibly-empty container and don’t want to write e.g. ‘maximum (0 : xs)’ to avoid partiality. For example, ‘foldMap (Minimum . Just &&& Maximum . Just)’ computes the minimum and maximum in a single pass, returning Nothing for a null input.

Any thoughts, critiques, questions, rebuttals, edge cases, use cases, &c. are most welcome!


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Min & Max without Bounded

Jon Purdy
In reply to this post by Jon Purdy

On Tue, Nov 10, 2020 at 1:33 PM Hécate <[hidden email]> wrote:
I invite you to read the source of this module, which may provide some context for the current situation:

https://hackage.haskell.org/package/base-4.14.0.0/docs/src/Data.Functor.Utils.html#%23

Thanks! That is useful context. I was aware that this had been considered as a possibility, but having coincidentally run into it multiple times recently, I figured I’d propose it in earnest. Since there are two reasonable ways to define these, I would prefer to have both available. There’s an analogous situation, albeit not for the same reasons afaik, with ‘First’ and ‘Last’ from ‘Data.Semigroup’ (without ‘Maybe’) and ‘Data.Monoid’ (with ‘Maybe’). I wonder if that’s considered a good example to follow (i.e. adding ‘Data.Monoid.{Max,Min}’) or more an unfortunate side effect of how ‘semigroups’ was integrated into ‘base’.

On Tue, Nov 10, 2020 at 1:58 PM Oleg Grenrus <[hidden email]> wrote:
Aren't Min and Max sufficient by themselves:

>>> foldMap (Just . Min &&& Just . Max) [1..10 :: Integer]
(Just (Min {getMin = 1}),Just (Max {getMax = 10}))

That’s a good point, and also a suitable alternative, thank you. I guess the main question is whether anyone thinks it important enough to add a separate type to provide different instances than this, for example ‘Ord’ as I mentioned. If not, I’m satisfied to use this or my own definitions.

(More likely the latter, if anyone’s curious, since I also have parallel types for lower & upper bounds, where ‘Nothing’ is a zero rather than a unit.)


On Tue, Nov 10, 2020 at 1:19 PM Jon Purdy <[hidden email]> wrote:
The ‘Min’ and ‘Max’ newtypes from ‘Data.Semigroup’ require a ‘Bounded’ constraint for their ‘Monoid’ instances, because they wrap a value of type ‘a’ rather than ‘Maybe a’. The latter is something I’ve needed a few times lately for types that cannot be bounded (or just don’t want to be), and it seems base-worthy to me.

So if it hasn’t been proposed and dismissed already (or if it has been proposed, but just got backburnered), I’d like to suggest adding the following new types, and at least the following instances, to either ‘Data.Semigroup’ or ‘Data.Monoid’:

newtype Minimum a = Minimum { getMinimum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Down (Maybe (Down (Max a))))

newtype Maximum a = Maximum { getMaximum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Maybe (Max a))

These instances could be specified explicitly instead of with ‘DerivingVia’, but the essential notion here is that ‘Maximum’ uses its empty value ‘Nothing’ as a “negative infinity”, less than all other ‘Just’ values, while ‘Minimum’ reverses that to use ‘Nothing’ as the corresponding “positive infinity”.

I assume these would also want more or less the same array of instances as for the existing ‘Min’ and ‘Max’, and there is the usual opportunity to bikeshed about the naming and how these instances ought to relate to one another. I figure that at least the ‘Ord’ instance should match the ordering that the ‘Semigroup’ instance uses, to avoid some potential for confusion/bugs.

As for use cases, I tend to reach for these things either in configuration records with many fields that I’m combining by semigroup append (Max, Any, set union, and so on) or when I have a possibly-empty container and don’t want to write e.g. ‘maximum (0 : xs)’ to avoid partiality. For example, ‘foldMap (Minimum . Just &&& Maximum . Just)’ computes the minimum and maximum in a single pass, returning Nothing for a null input.

Any thoughts, critiques, questions, rebuttals, edge cases, use cases, &c. are most welcome!


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Min & Max without Bounded

Oleg Grenrus

In lattices [1] package there is an Ordered newtype with a Lattice instance where (/\) = min and (\/) = max. Then one can add extra elements with Lifted (extra bottom), Dropped (extra top) or Levitated (extra top and bottom). Finally one can convert a Lattice into Monoid with Meet or Join newtypes.

This is to point out that there are various ways to think about this issue. I don't think we have found /the/ solution to be in "base".

[1] https://hackage.haskell.org/package/lattices

On 11.11.2020 0.57, Jon Purdy wrote:

On Tue, Nov 10, 2020 at 1:33 PM Hécate <[hidden email]> wrote:
I invite you to read the source of this module, which may provide some context for the current situation:

https://hackage.haskell.org/package/base-4.14.0.0/docs/src/Data.Functor.Utils.html#%23

Thanks! That is useful context. I was aware that this had been considered as a possibility, but having coincidentally run into it multiple times recently, I figured I’d propose it in earnest. Since there are two reasonable ways to define these, I would prefer to have both available. There’s an analogous situation, albeit not for the same reasons afaik, with ‘First’ and ‘Last’ from ‘Data.Semigroup’ (without ‘Maybe’) and ‘Data.Monoid’ (with ‘Maybe’). I wonder if that’s considered a good example to follow (i.e. adding ‘Data.Monoid.{Max,Min}’) or more an unfortunate side effect of how ‘semigroups’ was integrated into ‘base’.

On Tue, Nov 10, 2020 at 1:58 PM Oleg Grenrus <[hidden email]> wrote:
Aren't Min and Max sufficient by themselves:

>>> foldMap (Just . Min &&& Just . Max) [1..10 :: Integer]
(Just (Min {getMin = 1}),Just (Max {getMax = 10}))

That’s a good point, and also a suitable alternative, thank you. I guess the main question is whether anyone thinks it important enough to add a separate type to provide different instances than this, for example ‘Ord’ as I mentioned. If not, I’m satisfied to use this or my own definitions.

(More likely the latter, if anyone’s curious, since I also have parallel types for lower & upper bounds, where ‘Nothing’ is a zero rather than a unit.)


On Tue, Nov 10, 2020 at 1:19 PM Jon Purdy <[hidden email]> wrote:
The ‘Min’ and ‘Max’ newtypes from ‘Data.Semigroup’ require a ‘Bounded’ constraint for their ‘Monoid’ instances, because they wrap a value of type ‘a’ rather than ‘Maybe a’. The latter is something I’ve needed a few times lately for types that cannot be bounded (or just don’t want to be), and it seems base-worthy to me.

So if it hasn’t been proposed and dismissed already (or if it has been proposed, but just got backburnered), I’d like to suggest adding the following new types, and at least the following instances, to either ‘Data.Semigroup’ or ‘Data.Monoid’:

newtype Minimum a = Minimum { getMinimum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Down (Maybe (Down (Max a))))

newtype Maximum a = Maximum { getMaximum :: Maybe a }
  deriving (Eq, Ord, Monoid, Semigroup)
    via (Maybe (Max a))

These instances could be specified explicitly instead of with ‘DerivingVia’, but the essential notion here is that ‘Maximum’ uses its empty value ‘Nothing’ as a “negative infinity”, less than all other ‘Just’ values, while ‘Minimum’ reverses that to use ‘Nothing’ as the corresponding “positive infinity”.

I assume these would also want more or less the same array of instances as for the existing ‘Min’ and ‘Max’, and there is the usual opportunity to bikeshed about the naming and how these instances ought to relate to one another. I figure that at least the ‘Ord’ instance should match the ordering that the ‘Semigroup’ instance uses, to avoid some potential for confusion/bugs.

As for use cases, I tend to reach for these things either in configuration records with many fields that I’m combining by semigroup append (Max, Any, set union, and so on) or when I have a possibly-empty container and don’t want to write e.g. ‘maximum (0 : xs)’ to avoid partiality. For example, ‘foldMap (Minimum . Just &&& Maximum . Just)’ computes the minimum and maximum in a single pass, returning Nothing for a null input.

Any thoughts, critiques, questions, rebuttals, edge cases, use cases, &c. are most welcome!


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries