[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