Re: HasCallStack constraint for maximum in Prelude?
Adding that warning won't affect performance, but it would guide people to never use them (Especially Foldable variant; its type is wrong - it would make some sense with Bounded - if Bounded were related to Ord).
For (Bounded a, Ord a) one should replace maximum by an ordinary fold, given a suitable newtype wrapper with a Monoid instance, e.g. (Ord a, Bounded a) => Monoid (Data.Semigroup.Max a) In the past I've also used custom newtypes if e.g. I knew all values were non-negative and the least value I cared about was zero. -- Olaf
On Wed, Mar 25, 2026 at 11:19:20AM +0100, Olaf Klinke via Haskell-Cafe wrote:
For (Bounded a, Ord a) one should replace maximum by an ordinary fold, given a suitable newtype wrapper with a Monoid instance, e.g.
(Ord a, Bounded a) => Monoid (Data.Semigroup.Max a)
For a Semigroup surely it doesn't need Bounded? (A Monoid would need Bounded). Tom
On Wed, Mar 25, 2026 at 10:23:37AM +0000, Tom Ellis wrote:
On Wed, Mar 25, 2026 at 11:19:20AM +0100, Olaf Klinke via Haskell-Cafe wrote:
For (Bounded a, Ord a) one should replace maximum by an ordinary fold, given a suitable newtype wrapper with a Monoid instance, e.g.
(Ord a, Bounded a) => Monoid (Data.Semigroup.Max a)
For a Semigroup surely it doesn't need Bounded? (A Monoid would need Bounded).
What Olaf seems to have in mind does neet a Monoid, for example: λ> import Data.Coerce λ> import Data.Foldable λ> import Data.Semigroup λ> λ> minimum :: forall t a. (Ord a, Bounded a, Foldable t) => t a -> a; minimum = (coerce @(Min a) @a) . foldMap' (coerce @a @(Min a)) λ> maximum :: forall t a. (Ord a, Bounded a, Foldable t) => t a -> a; maximum = (coerce @(Max a) @a) . foldMap' (coerce @a @(Max a)) λ> λ> maximum [] :: Int -9223372036854775808 λ> minimum [] :: Int 9223372036854775807 λ> maximum [] :: Word 0 λ> minimum [] :: Word 18446744073709551615 λ> maximum [1..10] :: Int 10 λ> minimum [1..10] :: Int 1 -- Viktor. 🇺🇦 Слава Україні!
On Wed, Mar 25, 2026 at 11:56:21PM +1100, Viktor Dukhovni wrote:
What Olaf seems to have in mind does neet a Monoid, for example:
λ> import Data.Coerce λ> import Data.Foldable λ> import Data.Semigroup λ> λ> minimum :: forall t a. (Ord a, Bounded a, Foldable t) => t a -> a; minimum = (coerce @(Min a) @a) . foldMap' (coerce @a @(Min a)) λ> maximum :: forall t a. (Ord a, Bounded a, Foldable t) => t a -> a; maximum = (coerce @(Max a) @a) . foldMap' (coerce @a @(Max a))
The downside is that of course collections of `String` or other natural unbounded types would then no longer support `maximum` or `minimum`. [ Strings are of course bounded below by "", but I am not aware of standard type classes for one-sided bounds. ] A different interface would be needed for collections of unbounded elements that either returns a 'Maybe a' type, or a sentinel value of the user's choice, or accepts only non-empty foldable types. {-# LANGUAGE ViewPatterns #-} maximum' :: (Ord a, Foldable t) => t a -> Maybe a maximum' (toList -> (x : xs)) = Just $! foldl' max x xs maximum' _ = Nothing minimum' :: (Ord a, Foldable t) => t a -> Maybe a minimum' (toList -> (x : xs)) = Just $! foldl' min x xs minimum' _ = Nothing λ> minimum' [] Nothing λ> maximum' [] :: Maybe String Nothing λ> minimum' ["foo", "bar"] :: Maybe String Just "bar" λ> maximum' ["foo", "bar"] :: Maybe String Just "foo" -- Viktor. 🇺🇦 Слава Україні!
On Wed, Mar 25, 2026 at 11:56:21PM +1100, Viktor Dukhovni wrote:
On Wed, Mar 25, 2026 at 10:23:37AM +0000, Tom Ellis wrote:
On Wed, Mar 25, 2026 at 11:19:20AM +0100, Olaf Klinke via Haskell-Cafe wrote:
For (Bounded a, Ord a) one should replace maximum by an ordinary fold, given a suitable newtype wrapper with a Monoid instance, e.g.
(Ord a, Bounded a) => Monoid (Data.Semigroup.Max a)
For a Semigroup surely it doesn't need Bounded? (A Monoid would need Bounded).
What Olaf seems to have in mind does neet a Monoid, for example:
Yes, I misunderstood the type `Data.Semigroup.Max` (which just happens to live in `Data.Semigroup`) for the `Semigroup` class. I missed that the class is actually `Monoid`. Tom
On Wed, Mar 25, 2026 at 02:47:35PM +0000, Tom Ellis wrote:
What Olaf seems to have in mind does neet a Monoid, for example:
Yes, I misunderstood the type `Data.Semigroup.Max` (which just happens to live in `Data.Semigroup`) for the `Semigroup` class. I missed that the class is actually `Monoid`.
Mind you there's a conflict between the suggested `foldMap'` implementation for Bounded elements and efficient implementations with ordered Foldable collections (Map, IntMap, Set, ...). The latter, just return quickly return the least or greatest element without paying a linear cost to perform the proposed fold. So in practice, in code that is polymorphic in the container type, it is better to define: minimumMaybe :: (Ord a, Foldable t) => t a -> Maybe a minimumMaybe (null -> True) = Nothing minimumMaybe xs = Just $! minimum xs maximumMaybe :: (Bounded a, Ord a, Foldable t) => t a -> a maximumMaybe (null -> True) = Nothing maximumMaybe xs = Just $! maximum xs and these can then be leveraged to yield: boundedMinimum :: (Bounded a, Ord a, Foldable t) => t a -> a boundedMinimum (minimumMaybe -> Just a) = a boundedMinimum _ = maxBound boundedMaximum :: (Bounded a, Ord a, Foldable t) => t a -> a boundedMaximum (maximumMaybe -> Just a) = a boundedMaximum _ = minBound and variants for semi-lattices, e.g. natMaximum :: Foldable t => t Natural -> Natural natMaximum (maximumMaybe -> Just n) = n natMaximum _ = 0 -- Viktor. 🇺🇦 Слава Україні!
On Thu, Mar 26, 2026 at 05:10:59PM +1100, Viktor Dukhovni wrote:
So in practice, in code that is polymorphic in the container type, it is better to define:
minimumMaybe :: (Ord a, Foldable t) => t a -> Maybe a minimumMaybe (null -> True) = Nothing minimumMaybe xs = Just $! minimum xs
maximumMaybe :: (Bounded a, Ord a, Foldable t) => t a -> a maximumMaybe (null -> True) = Nothing maximumMaybe xs = Just $! maximum xs
Sorry about the cut/paste error, that should have been: maximumMaybe :: (Ord a, Foldable t) => t a -> Maybe a -- Viktor. 🇺🇦 Слава Україні!
participants (3)
-
Olaf Klinke -
Tom Ellis -
Viktor Dukhovni