Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Ord method defaults #24

Closed
treeowl opened this issue Dec 9, 2021 · 22 comments
Closed

Change Ord method defaults #24

treeowl opened this issue Dec 9, 2021 · 22 comments
Labels
approved Approved by CLC vote base-4.18 Implemented in base-4.18 (GHC 9.6)

Comments

@treeowl
Copy link

treeowl commented Dec 9, 2021

Currently, Ord is defined

class  (Eq a) => Ord a  where
    compare              :: a -> a -> Ordering
    (<), (<=), (>), (>=) :: a -> a -> Bool
    max, min             :: a -> a -> a

    compare x y = if x == y then EQ
                  -- NB: must be '<=' not '<' to validate the
                  -- above claim about the minimal things that
                  -- can be defined for an instance of Ord:
                  else if x <= y then LT
                  else GT

    x <  y = case compare x y of { LT -> True;  _ -> False }
    x <= y = case compare x y of { GT -> False; _ -> True }
    x >  y = case compare x y of { GT -> True;  _ -> False }
    x >= y = case compare x y of { LT -> False; _ -> True }

        -- These two default methods use '<=' rather than 'compare'
        -- because the latter is often more expensive
    max x y = if x <= y then y else x
    min x y = if x <= y then x else y
    {-# MINIMAL compare | (<=) #-}

The note about defining max and min using <= rather than compare actually applies to the other methods too. I propose we redefine them thus:

x >= y = y <= x
x > y = not (x <= y)
x < y = not (y <= x)

With these changes, no one should ever have to define a custom >=, >, or < to get optimal performance in optimized code.

What can go wrong? If someone defines <= in terms of either >=, >, or <, and lets the latter have its default implementation, then they'll end up in an infinite loop. This seems fairly unlikely to me, since there's absolutely no reason for anyone to do that.

Note that in some cases, certain methods may get lazier. Suppose we have

data Nat = Z | S Nat
  deriving Eq
instance Ord Nat where
  Z <= _ = True
  _ <= Z = False
  S m <= S n = m <= n

<= is lazy in its second argument: Z <= undefined = True. With the current defaults, all the other comparison methods are strict. However, with the proposed defaults, >= and < will be lazy in their second argument and > will be lazy in its first argument.

@googleson78
Copy link
Contributor

If someone defines <= in terms of either >=, >, or <, and lets the latter have its default implementation, then they'll end up in an infinite loop. This seems fairly unlikely to me, since there's absolutely no reason for anyone to do that.

This also infinitely loops right now. Do you mean "if one defines compare and also defines (<=) in terms of..."?

@treeowl
Copy link
Author

treeowl commented Dec 11, 2021

Yes, I'm referring to a proper implementation of compare along with a useless definition of <= in terms of methods I propose to change.

@Bodigrim
Copy link
Collaborator

@treeowl if you'd like to move this forward, please link an MR.

@Bodigrim Bodigrim added the awaits-MR No GHC MR (https://gitlab.haskell.org/ghc/ghc/-/merge_requests) has been raised yet label Mar 22, 2022
@Bodigrim
Copy link
Collaborator

@treeowl just a gentle reminder that this proposal needs an MR before holding a vote.

@Bodigrim
Copy link
Collaborator

Bodigrim commented Aug 5, 2022

@treeowl unless there is a progress with your proposal within two weeks, I'll close it as abandoned.

@treeowl
Copy link
Author

treeowl commented Aug 5, 2022

I'll try to do something about it this weekend.

@Bodigrim
Copy link
Collaborator

Bodigrim commented Aug 9, 2022

@treeowl just a gentle reminder.

Or maybe someone else can help us to move this change to class Ord forward?

@treeowl
Copy link
Author

treeowl commented Aug 9, 2022

@Bodigrim If someone has the time, I'd be very grateful.

@tbidne
Copy link

tbidne commented Aug 10, 2022

@treeowl @Bodigrim created an MR here.

@Bodigrim Bodigrim removed the awaits-MR No GHC MR (https://gitlab.haskell.org/ghc/ghc/-/merge_requests) has been raised yet label Aug 12, 2022
@Bodigrim
Copy link
Collaborator

Thanks @tbidne!

Dear CLC members, let's vote on the proposal to express default implementations of >=, > and < in terms of <= instead of compare, https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8811.
@tomjaguarpaw @chessai @emilypi @cgibbard @mixphix


+1 from me.

@mixphix
Copy link
Collaborator

mixphix commented Aug 12, 2022

+1

@emilypi
Copy link
Member

emilypi commented Aug 13, 2022

+1. ezpz.

@treeowl
Copy link
Author

treeowl commented Aug 13, 2022

@treeowl @Bodigrim created an MR here.

Thanks, @tbidne! My low capacity has been exceeded by other priorities recently; I appreciate your help moving this along.

@cgibbard
Copy link
Contributor

It logically makes good sense that it should be easier for the compiler to get better code out of this, but can we somehow quickly confirm that it really happens before pulling the trigger? If so, I'm +1. I really hope there are no libraries out there which would be negatively impacted by this (though if anyone has a good way to search Hackage, that'd be awesome) -- it's an easy fix in any case, but it'll be a nontermination bug that will only show up at runtime if someone really does have an offending definition.

@treeowl
Copy link
Author

treeowl commented Aug 13, 2022

@cgibbard this is really about semantics, not performance.

@tomjaguarpaw
Copy link
Member

-1


Proposer @treeowl mentions twice that it's about performance and doesn't mention semantics in the original proposal

The note about defining max and min using <= rather than compare actually applies to the other methods too

With these changes, no one should ever have to define a custom >=, >, or < to get optimal performance in optimized code.

yet then says it's about semantics, not performance. So I'm confused and unable to see what the purported benefit is.

@treeowl
Copy link
Author

treeowl commented Aug 15, 2022

Proposer will think about that again and try to remember.

@Bodigrim
Copy link
Collaborator

Bodigrim commented Aug 20, 2022

Here is a current definition of instance Ord [a] from ghc-prim:

instance (Ord a) => Ord [a] where
    compare []     []     = EQ
    compare []     (_:_)  = LT
    compare (_:_)  []     = GT
    compare (x:xs) (y:ys) = case compare x y of
                                EQ    -> compare xs ys
                                other -> other

The definition of compare forces both arguments to WHNF, and so is the default definition of <= and other operations. This is arguably suboptimal: I would not expect [] <= xs to evaluate xs ever. Even if we add a hand-written definition of <= to instance Ord [a], it would not make <, >, >= lazier, because their default definitions are in terms of compare, not <=. The proposal aims to change the latter fact, allowing to define lazier instance Ord with less boilerplate.

With regards to performance, the very next instance in ghc-prim is revealing:

-- We don't use deriving for Ord Char, because for Ord the derived
-- instance defines only compare, which takes two primops.  Then
-- '>' uses compare, and therefore takes two primops instead of one.
instance Ord Char where
    (C# c1) >  (C# c2) = isTrue# (c1 `gtChar#` c2)
    (C# c1) >= (C# c2) = isTrue# (c1 `geChar#` c2)
    (C# c1) <= (C# c2) = isTrue# (c1 `leChar#` c2)
    (C# c1) <  (C# c2) = isTrue# (c1 `ltChar#` c2)

Under the proposal it would be enough to define (C# c1) <= (C# c2) = isTrue# (c1 `leChar#` c2), and everything else would just follow automatically without compromising performance.

@tomjaguarpaw @cgibbard does it help?

@Bodigrim
Copy link
Collaborator

@cgibbard does my explanation help to convert your conditional vote into an unconditional one?
@tomjaguarpaw shall I consider your vote as final?

@tomjaguarpaw
Copy link
Member

+1


I change my vote. Thanks @Bodigrim, your explanation shows that the merit of this change with respect to performance.

(For posterity: This will help get better performance by default, which is great, but I still remain somewhat skeptical of the robustness of relying on default methods if you want good performance.)

@Bodigrim
Copy link
Collaborator

OK, this gives us at least 4 votes in favor. Approved, thanks all.

@Bodigrim Bodigrim added the approved Approved by CLC vote label Aug 31, 2022
ghc-mirror-bot pushed a commit to ghc/ghc that referenced this issue Sep 1, 2022
ghc-mirror-bot pushed a commit to ghc/ghc that referenced this issue Sep 1, 2022
ghc-mirror-bot pushed a commit to ghc/ghc that referenced this issue Sep 1, 2022
@chshersh
Copy link
Member

I'm trying to summarise the state of this proposal as part of my volunteering effort to track the progress of all approved CLC proposals.

Field Value
Author @treeowl, @tbidne
Status merged
base version 4.18.0.0
Merge Request (MR) https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8811
Blocked by nothing
CHANGELOG entry present
Migration guide not needed

Please, let me know if you find any mistakes 🙂

@chshersh chshersh added the base-4.18 Implemented in base-4.18 (GHC 9.6) label Mar 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Approved by CLC vote base-4.18 Implemented in base-4.18 (GHC 9.6)
Projects
None yet
Development

No branches or pull requests

9 participants