Oh yes, for some reason I was thinking maximumBy
took a a -> a -> a
which chooses the maximum!
What i understand is that on one side some people (including me and the original haskell report) donât really mind which the biais is as long as min
and max
have the opposite so that in effect min a b /= max a b
(at pointer equality level) on the other side some might say if things are Eq
then the biais is irrelevant (if it matters then you something to break the tie). Both way can be reconciled by saying that we care for min
and max
only. If things are in a list or any fodable then the output is not predictable .
I can absolutely respect that people have different intuitions around the behavior of the binary min
and max
. But nobody is served by saying that the behavior of minimum
and maximum
and *By
is not predictable. This isnât an interface where leaving things undefined frees implementers up to make choices that are good for their part of the ecosystem. Iâm only calling for change so that things can be more predictable! Who is on the side of unpredictabilityâwho would oppose me by saying no, this shouldnât change because I would prefer it to be unpredictable? If you like unpredictability so much, change shouldnât bother you! If you donât, why object?
I was today years old when I learned that max was right-biased.
I think no matter if you prefer right-bias or left-bias, having clear documentation about the behavior might be a good idea.
If the reactions to this change are split, you could have a function called maxr or maxl as a potential compromise.
Personally, I think max should remain right-biased, only because switching to left-bias might lead to silent breakage.
Would you be equally concerned about silent breaks if we kept max
as-is but made Semigroup (Max a)
, maximum
and maximumBy
left-biased?
Thatâs something I can get behind (:
Itâs a property of a sorted list that max
on any two elements of it taken in the sorted order returns the right hand one. Making max
left leaning would break that property because it wouldnât respect the order given by sort. I think your way of doing it is going to be less intuitive.
Itâs a property of a reverse-sorted list that max
on any two elements of it taken in the reverse-sorted order returns the left-hand one⌠except it isnât, because max
happens to be right-biased.
Itâs also a property of a reverse-sorted list that min
on any two elements of it taken in the reverse-sorted order returns the right-hand one⌠except it isnât, because min
is left-biased.
The existence of types for which equality is not identity fundamentally causes you to surrender approximately half of all of the statements of the above form that you could imagine making. Is there any practical reason to prefer the ones we have over the ones weâd get by making min
and max
consistent?
Maybe leaving it unpredictable will force people who do need predictability to use a tie breaker, example fst . maximum . flip zip [1..]
instead of relying on implementation potentially savind them from bugs (in case of misbehaving implementation).
Personally, I donât think itâs clear that left-biasing max
is unambiguously the correct thing to do. Iâm pretty happy with max
returning the element min
didnât, and as far as maximum
and minimum
go, if you need a property stronger than âthe returned element is greater (or less) than or equal to all other elements in the collectionâ (for example, that it is the first/last such element), you should be using a different function which is explicit about that.
In addition, even if it was an unambiguously beneficial change, changing the existing behaviour may break existing code in a subtle way. Itâs not something like removing fail
from Monad
where the affected code now has compile errors.
What if changed the description for Ord to stand for total preorders instead of total orders: we allowed a == b = True
and a <= b && b <= a = True
for distinct a
and b
? Then the questions like if a sort is stable, or which instance should a function return out of equivalent candidates would be lawful territory instead of unlawful/meaningless.
Thereâs no order versus preorder confusion here. Relations represented by Ord
do have to satisfy antisymmetryâa >= b && b >= a
implies a == b
.
The fact that a == b
doesnât imply that a
is observationally identical to b
isnât represented in the mathematical relational hierarchy, at least not the one involving traditional orders and preorders (maybe a category theorist has a richer toolkit of relation-like things over gadgets defined only up to equivalence or something).
I donât think that for example (<=) = \x y -> True
is a total order on Int
, at least in the mathematical terminology. Int
with (==) = \x y -> True
, (<=) = \x y -> True
would be a perfectly fine Ord, if not the description of Ord.
I am not sure what was their intent in the Haskell2010 report, I tried to find instances of Ord that are not total orders, but couldnât find any.
It definitely is not, given the standard instance of Eq Int
. But if you asked me whether, given (==) = \x y -> True
, (<=) = \x y -> True
defines a total order on Int
(or on any other type), Iâd have to say yes.
This confusion appeared in the linked issue #15921: Data.List.maximumBy uses counter-intuitive ordering ¡ Issues ¡ Glasgow Haskell Compiler / GHC ¡ GitLab
although I donât agree waldmann there, maximumBy (compare 'on' snd)
should work regardless if the laws of Ord need total ordering or just a preordering.
So, itâs correct that compare `on` snd
doesnât generally define a total ordering, given the Eq (a, b)
instance likely to be in play (though it does if a
is ()
, for example). But the documentation for maximumBy
doesnât (currently?) state that the comparison function provided needs to define a total ordering, nor should it. As long as the Ord (a, b)
instance isnât defined using compare `on` snd
, no foul.
Oh, right, waldmann most certainly meant that compare 'on' snd
equality is not the same as (Int,Int)
equality. It seems I am alone with my confusion.
But if you asked me whether, given
(==) = \x y -> True
,(<=) = \x y -> True
defines a total order onInt
(or on any other type), Iâd have to say yes.
Is there a reason why should Haskell (==)
play the role of math =
? For me it is more logical for Haskell =
to play the role of math =
, and for Haskell (==)
to play the role of math ==
. For example in the case of Arg a b
I would say that (==) =
is an equivalence relation, and (<=)
is a total preorder. That is my mathematical model, and it would be nice to use the same words, with the same meaning. Also nothing else cares about (==)
on Arg a b
and apart from ordering, everything else treats Arg 0 0
and Arg 0 1
differently, so it is weird to say that Arg 0 0 = Arg 0 1
in math, and antisymmetry holds.
edit: in summary, I think that that antisymmetry law is for (<=)
to be a total preorder, and not a total order.
I think this is the crux of our disagreement, but thereâs a lot of ambiguity smuggled into this statement. For me to go on this journey with you, Iâd need you to define what you mean by âHaskell =
â, âmath =
â, and âmath ==
â, the first two of which I think are overloaded concepts, and the last of which I havenât seen without some prior definition.
This also might be better as a distinct thread if you want to continue the conversation; I donât think weâd come back to the question of max
, maximum
, etc. from that direction.
Iâve been reflecting on this for a few days and I have to admit, itâs sounding more compelling the more I think about it. Maybe itâs best to consider all of these functions as using argument-order or structure-order as the tie-breaker if Ord
-order doesnât distinguish between elements, instead of thinking about âbias directionâ as I have been.
The reason for the different bias is simple: if you do (min x y, max x y) you know that the pair will contain either (x, y) or (y, x).
Maybe the reason is not spelt out in the report, but it was carefully designed that way.