[Git][ghc/ghc][master] base: specify tie-breaking behavior of min, max, and related list/Foldable functions
Marge Bot (@marge-bot)
gitlab at gitlab.haskell.org
Fri May 24 11:51:18 UTC 2024
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
a3e0b68b by Ryan Hendrickson at 2024-05-23T15:52:03-04:00
base: specify tie-breaking behavior of min, max, and related list/Foldable functions
- - - - -
7 changed files:
- libraries/base/src/Data/Bifoldable.hs
- libraries/base/src/Data/Foldable1.hs
- libraries/base/src/Data/Semigroup.hs
- libraries/ghc-internal/src/GHC/Internal/Data/Foldable.hs
- libraries/ghc-internal/src/GHC/Internal/Data/OldList.hs
- libraries/ghc-internal/src/GHC/Internal/List.hs
- libraries/ghc-prim/GHC/Classes.hs
Changes:
=====================================
libraries/base/src/Data/Bifoldable.hs
=====================================
@@ -640,7 +640,13 @@ bielem x = biany (== x) (== x)
biconcat :: Bifoldable t => t [a] [a] -> [a]
biconcat = bifold
--- | The largest element of a non-empty structure.
+-- | The largest element of a non-empty structure. This function is equivalent
+-- to @'bifoldr1' 'max'@, and its behavior on structures with multiple largest
+-- elements depends on the relevant implementation of 'max'. For the default
+-- implementation of 'max' (@max x y = if x <= y then y else x@), structure
+-- order is used as a tie-breaker: if there are multiple largest elements, the
+-- rightmost of them is chosen (this is equivalent to @'bimaximumBy'
+-- 'compare'@).
--
-- ==== __Examples__
--
@@ -670,7 +676,13 @@ bimaximum = fromMaybe (error "bimaximum: empty structure") .
getMax . bifoldMap mj mj
where mj = Max #. (Just :: a -> Maybe a)
--- | The least element of a non-empty structure.
+-- | The least element of a non-empty structure. This function is equivalent to
+-- @'bifoldr1' 'min'@, and its behavior on structures with multiple least
+-- elements depends on the relevant implementation of 'min'. For the default
+-- implementation of 'min' (@min x y = if x <= y then x else y@), structure
+-- order is used as a tie-breaker: if there are multiple least elements, the
+-- leftmost of them is chosen (this is equivalent to @'biminimumBy'
+-- 'compare'@).
--
-- ==== __Examples__
--
@@ -927,7 +939,8 @@ biall :: Bifoldable t => (a -> Bool) -> (b -> Bool) -> t a b -> Bool
biall p q = getAll #. bifoldMap (All . p) (All . q)
-- | The largest element of a non-empty structure with respect to the
--- given comparison function.
+-- given comparison function. Structure order is used as a tie-breaker: if
+-- there are multiple largest elements, the rightmost of them is chosen.
--
-- ==== __Examples__
--
@@ -956,7 +969,8 @@ bimaximumBy cmp = bifoldr1 max'
_ -> y
-- | The least element of a non-empty structure with respect to the
--- given comparison function.
+-- given comparison function. Structure order is used as a tie-breaker: if
+-- there are multiple least elements, the leftmost of them is chosen.
--
-- ==== __Examples__
--
=====================================
libraries/base/src/Data/Foldable1.hs
=====================================
@@ -130,7 +130,13 @@ class Foldable t => Foldable1 t where
toNonEmpty :: t a -> NonEmpty a
toNonEmpty = runNonEmptyDList . foldMap1 singleton
- -- | The largest element of a non-empty structure.
+ -- | The largest element of a non-empty structure. This function is
+ -- equivalent to @'foldr1' 'Data.Ord.max'@, and its behavior on structures
+ -- with multiple largest elements depends on the relevant implementation of
+ -- 'Data.Ord.max'. For the default implementation of 'Data.Ord.max' (@max x
+ -- y = if x <= y then y else x@), structure order is used as a tie-breaker:
+ -- if there are multiple largest elements, the rightmost of them is chosen
+ -- (this is equivalent to @'maximumBy' 'Data.Ord.compare'@).
--
-- >>> maximum (32 :| [64, 8, 128, 16])
-- 128
@@ -139,7 +145,13 @@ class Foldable t => Foldable1 t where
maximum :: Ord a => t a -> a
maximum = getMax #. foldMap1' Max
- -- | The least element of a non-empty structure.
+ -- | The least element of a non-empty structure. This function is
+ -- equivalent to @'foldr1' 'Data.Ord.min'@, and its behavior on structures
+ -- with multiple largest elements depends on the relevant implementation of
+ -- 'Data.Ord.min'. For the default implementation of 'Data.Ord.min' (@min x
+ -- y = if x <= y then x else y@), structure order is used as a tie-breaker:
+ -- if there are multiple least elements, the leftmost of them is chosen
+ -- (this is equivalent to @'minimumBy' 'Data.Ord.compare'@).
--
-- >>> minimum (32 :| [64, 8, 128, 16])
-- 8
@@ -362,7 +374,8 @@ foldlMapM1 g f t = g x >>= \y -> foldlM f y xs
where x:|xs = toNonEmpty t
-- | The largest element of a non-empty structure with respect to the
--- given comparison function.
+-- given comparison function. Structure order is used as a tie-breaker: if
+-- there are multiple largest elements, the rightmost of them is chosen.
--
-- @since 4.18.0.0
maximumBy :: Foldable1 t => (a -> a -> Ordering) -> t a -> a
@@ -372,7 +385,8 @@ maximumBy cmp = foldl1' max'
_ -> y
-- | The least element of a non-empty structure with respect to the
--- given comparison function.
+-- given comparison function. Structure order is used as a tie-breaker: if
+-- there are multiple least elements, the leftmost of them is chosen.
--
-- @since 4.18.0.0
minimumBy :: Foldable1 t => (a -> a -> Ordering) -> t a -> a
=====================================
libraries/base/src/Data/Semigroup.hs
=====================================
@@ -340,7 +340,11 @@ instance Num a => Num (Max a) where
fromInteger = Max . fromInteger
-- | 'Arg' isn't itself a 'Semigroup' in its own right, but it can be
--- placed inside 'Min' and 'Max' to compute an arg min or arg max.
+-- placed inside 'Min' and 'Max' to compute an arg min or arg max. In
+-- the event of ties, the leftmost qualifying 'Arg' is chosen; contrast
+-- with the behavior of 'minimum' and 'maximum' for many other types,
+-- where ties are broken by considering elements to the left in the
+-- structure to be less than elements to the right.
--
-- ==== __Examples__
--
@@ -397,11 +401,25 @@ instance Foldable (Arg a) where
instance Traversable (Arg a) where
traverse f (Arg x a) = Arg x `fmap` f a
--- | @since 4.9.0.0
+-- |
+-- Note that `Arg`'s 'Eq' instance does not satisfy extensionality:
+--
+-- >>> Arg 0 0 == Arg 0 1
+-- True
+-- >>> let f (Arg _ x) = x in f (Arg 0 0) == f (Arg 0 1)
+-- False
+--
+-- @since 4.9.0.0
instance Eq a => Eq (Arg a b) where
Arg a _ == Arg b _ = a == b
--- | @since 4.9.0.0
+-- |
+-- Note that `Arg`'s 'Ord' instance has 'min' and 'max' implementations that
+-- differ from the tie-breaking conventions of the default implementation of
+-- 'min' and 'max' in class 'Ord'; 'Arg' breaks ties by favoring the first
+-- argument in both functions.
+--
+-- @since 4.9.0.0
instance Ord a => Ord (Arg a b) where
Arg a _ `compare` Arg b _ = compare a b
min x@(Arg a _) y@(Arg b _)
=====================================
libraries/ghc-internal/src/GHC/Internal/Data/Foldable.hs
=====================================
@@ -555,7 +555,13 @@ class Foldable t where
elem :: Eq a => a -> t a -> Bool
elem = any . (==)
- -- | The largest element of a non-empty structure.
+ -- | The largest element of a non-empty structure. This function is
+ -- equivalent to @'foldr1' 'max'@, and its behavior on structures with
+ -- multiple largest elements depends on the relevant implementation of
+ -- 'max'. For the default implementation of 'max' (@max x y = if x <= y
+ -- then y else x@), structure order is used as a tie-breaker: if there are
+ -- multiple largest elements, the rightmost of them is chosen (this is
+ -- equivalent to @'maximumBy' 'compare'@).
--
-- This function is non-total and will raise a runtime exception if the
-- structure happens to be empty. A structure that supports random access
@@ -583,7 +589,13 @@ class Foldable t where
getMax . foldMap' (Max #. (Just :: a -> Maybe a))
{-# INLINEABLE maximum #-}
- -- | The least element of a non-empty structure.
+ -- | The least element of a non-empty structure. This function is
+ -- equivalent to @'foldr1' 'min'@, and its behavior on structures with
+ -- multiple largest elements depends on the relevant implementation of
+ -- 'min'. For the default implementation of 'min' (@min x y = if x <= y
+ -- then x else y@), structure order is used as a tie-breaker: if there are
+ -- multiple least elements, the leftmost of them is chosen (this is
+ -- equivalent to @'minimumBy' 'compare'@).
--
-- This function is non-total and will raise a runtime exception if the
-- structure happens to be empty. A structure that supports random access
@@ -1288,7 +1300,8 @@ all :: Foldable t => (a -> Bool) -> t a -> Bool
all p = getAll #. foldMap (All #. p)
-- | The largest element of a non-empty structure with respect to the
--- given comparison function.
+-- given comparison function. Structure order is used as a tie-breaker: if
+-- there are multiple largest elements, the rightmost of them is chosen.
--
-- ==== __Examples__
--
@@ -1314,7 +1327,8 @@ maximumBy cmp = fromMaybe (errorWithoutStackTrace "maximumBy: empty structure")
{-# INLINE[2] maximumBy #-}
-- | The least element of a non-empty structure with respect to the
--- given comparison function.
+-- given comparison function. Structure order is used as a tie-breaker: if
+-- there are multiple least elements, the leftmost of them is chosen.
--
-- ==== __Examples__
--
=====================================
libraries/ghc-internal/src/GHC/Internal/Data/OldList.hs
=====================================
@@ -958,10 +958,11 @@ insertBy cmp x ys@(y:ys')
GT -> y : insertBy cmp x ys'
_ -> x : ys
--- | The 'maximumBy' function is the non-overloaded version of 'maximum',
--- which takes a comparison function and a list
+-- | The 'maximumBy' function takes a comparison function and a list
-- and returns the greatest element of the list by the comparison function.
-- The list must be finite and non-empty.
+-- List order is used as a tie-breaker: if there are multiple greatest
+-- elements, the last of them is chosen.
--
-- ==== __Examples__
--
@@ -980,10 +981,11 @@ maximumBy cmp xs = foldl1 maxBy xs
GT -> x
_ -> y
--- | The 'minimumBy' function is the non-overloaded version of 'minimum',
--- which takes a comparison function and a list
+-- | The 'minimumBy' function takes a comparison function and a list
-- and returns the least element of the list by the comparison function.
-- The list must be finite and non-empty.
+-- List order is used as a tie-breaker: if there are multiple least
+-- elements, the first of them is chosen.
--
-- ==== __Examples__
--
=====================================
libraries/ghc-internal/src/GHC/Internal/List.hs
=====================================
@@ -808,8 +808,11 @@ scanr1 f (x:xs) = f x q : qs
-- | 'maximum' returns the maximum value from a list,
-- which must be non-empty, finite, and of an ordered type.
--- It is a special case of 'GHC.Internal.Data.List.maximumBy', which allows the
--- programmer to supply their own comparison function.
+-- This function is equivalent to @'foldr1' 'max'@, and its behavior on lists
+-- with multiple maxima depends on the relevant implementation of 'max'. For
+-- the default implementation of 'max', list order is used as a tie-breaker: if
+-- there are multiple maxima, the rightmost of them is chosen (this is
+-- equivalent to @'GHC.Internal.Data.List.maximumBy' 'compare'@).
--
-- >>> maximum []
-- *** Exception: Prelude.maximum: empty list
@@ -832,8 +835,11 @@ maximum xs = foldl1' max xs
-- | 'minimum' returns the minimum value from a list,
-- which must be non-empty, finite, and of an ordered type.
--- It is a special case of 'GHC.Internal.Data.List.minimumBy', which allows the
--- programmer to supply their own comparison function.
+-- This function is equivalent to @'foldr1' 'min'@, and its behavior on lists
+-- with multiple minima depends on the relevant implementation of 'min'. For
+-- the default implementation of 'min', list order is used as a tie-breaker: if
+-- there are multiple minima, the leftmost of them is chosen (this is
+-- equivalent to @'GHC.Internal.Data.List.minimumBy' 'compare'@).
--
-- >>> minimum []
-- *** Exception: Prelude.minimum: empty list
=====================================
libraries/ghc-prim/GHC/Classes.hs
=====================================
@@ -393,7 +393,29 @@ instance Ord TyCon where
--
-- Note that (7.) and (8.) do /not/ require 'min' and 'max' to return either of
-- their arguments. The result is merely required to /equal/ one of the
--- arguments in terms of '(==)'.
+-- arguments in terms of '(==)'. Users who expect a stronger guarantee are advised
+-- to write their own min and/or max functions.
+--
+-- The nuance of the above distinction is not always fully internalized by
+-- developers, and in the past (tracing back to the Haskell 1.4 Report) the
+-- specification for 'Ord' asserted the stronger property that @(min x y, max x
+-- y) = (x, y)@ or @(y, x)@, or in other words, that 'min' and 'max' /will/
+-- return one of their arguments, using argument order as the tie-breaker if
+-- the arguments are equal by comparison. A few list and
+-- 'Data.Foldable.Foldable' functions have behavior that is best understood
+-- with this assumption in mind: all variations of @minimumBy@ and @maximumBy@
+-- (which can't use 'min' and 'max' in their implementations) are written such
+-- that @minimumBy 'compare'@ and @maximumBy 'compare'@ are respectively
+-- equivalent to @minimum@ and @maximum@ (which do use 'min' and 'max') only if
+-- 'min' and 'max' adhere to this tie-breaking convention. Otherwise, if there
+-- are multiple least or largest elements in a container, @minimum@ and
+-- @maximum@ may not return the /same/ one that @minimumBy 'compare'@ and
+-- @maximumBy 'compare'@ do (though they should return something that is
+-- /equal/). (This is relevant for types with non-extensional equality, like
+-- 'Data.Semigroup.Arg', but also in cases where the precise reference held
+-- matters for memory-management reasons.) Unless there is a reason to deviate,
+-- it is less confusing for implementors of 'Ord' to respect this same
+-- convention (as the default definitions of 'min' and 'max' do).
--
-- Minimal complete definition: either 'compare' or '<='.
-- Using 'compare' can be more efficient for complex types.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a3e0b68b0f44ea00ff49c578e33f589ef991e5c0
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a3e0b68b0f44ea00ff49c578e33f589ef991e5c0
You're receiving this email because of your account on gitlab.haskell.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20240524/b112768f/attachment-0001.html>
More information about the ghc-commits
mailing list