haskell / core-libraries-committee

96 stars 15 forks source link

Add Eq1, Ord1, Show1 and Read1 instances for fundamental representation types in GHC.Generics #273

Closed sergv closed 2 months ago

sergv commented 3 months ago

As described in https://gitlab.haskell.org/ghc/ghc/-/issues/24312, there's an easy to use Generically1 type for deriving Eq1, Ord1, etc instances for user datatypes that have Generic1 instance.

Unfortunately it doesn't work because Generically1 defers work to Eq1, Ord1, etc instances for M1, K1, :+:, :*:, etc types from GHC.Generics.

The proposal is to add missing instances to make deriving with Generically1 usable. Specifically, add Eq1, Ord1, Show1 and Read1 for V1, U1, Par1, Rec1, K1, M1, :+:, :*:, :.:, URec, UAddr, UChar, UDouble, UFloat, UInt, and UWord.

Since unboxed types don't have regular Read instance, it's proposed to omit Read1 for UAddr, UChar, UDouble, UFloat, UInt, and UWord.

Note that UAddr does not have regular Show instance so it's proposed to add it as well because Show1 requires it. Suggested implementation is to reuse Show instance for Ptr which known how to show Addr# that UAddr has inside.

sergv commented 3 months ago

I've created draft PR adding instances at https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12886. Note that I added Show instance for UAddr to be able to define Show1 for it. There's no Read1 for unboxed types so I didn't define Read1 for them.

I noticed that there's no Show1 and no Read1 for Generically1 so adding Show1 and Read1 mostly serves completeness aims but won't enable users that derive Show1 instance through Generically1. I suspect Show1/Read1 was not defined for Generically1 because it will just show all the basic types like :+:, K1, etc and would be quite different from the Show instance derived by GHC. But having Show1 for the basic types will allow users easily define Generically1-like wrapper than implements Show1 by showing the basic types so there is benefit.

If there’s consesnus that adding Show1 instance for Generically1 is desirable I can add it to the PR mentioned above.

With the following test program the effect of new instances could be investigated. The test type I exercises all non-unboxed basic Generic types and witnesses that Ord1 can be derived.

#!/usr/bin/env -S cabal run
{- cabal:
build-depends:
  , base
  , ghc-internal
default-language:
  GHC2021
-}

{-# LANGUAGE DerivingVia #-}

import Control.Applicative
import Data.Functor.Classes
import GHC.Generics
import GHC.Ptr
import Text.ParserCombinators.ReadP qualified

import GHC.Show
import Text.Read

import GHC.Internal.Text.Read (Read(..), parens, prec, step)
import GHC.Internal.Text.ParserCombinators.ReadPrec (ReadPrec, readPrec_to_S, readS_to_Prec, pfail)
import GHC.Internal.Text.Read (Read(..), parens, prec, step)
import GHC.Internal.Text.Read.Lex (Lexeme(..))
import GHC.Internal.Text.Show (showListWith)
import GHC.Internal.Read

instance Show (UAddr p) where
  showsPrec d (UAddr x) =
    showParen (d > GHC.Show.appPrec) $
      showString "UAddr {uAddr# = " . showsPrec 0 (Ptr x) . showChar '}'

-- | @since base-4.21.0.0
instance Eq1 V1 where
  liftEq _ = \_ _ -> True

-- | @since base-4.21.0.0
instance Ord1 V1 where
  liftCompare _ = \_ _ -> EQ

-- | @since base-4.21.0.0
instance Show1 V1 where
  liftShowsPrec _ _ _ = \_ -> showString "V1"

-- | @since base-4.21.0.0
instance Read1 V1 where
  liftReadsPrec _ _ = readPrec_to_S pfail
  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance Eq1 U1 where
  liftEq _ = \_ _ -> True

-- | @since base-4.21.0.0
instance Ord1 U1 where
  liftCompare _ = \_ _ -> EQ

-- | @since base-4.21.0.0
instance Show1 U1 where
  liftShowsPrec _ _ _ = \U1 -> showString "U1"

-- | @since base-4.21.0.0
instance Read1 U1 where
  liftReadPrec _ _ =
    parens (expectP (Ident "U1") *> pure U1)

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance Eq1 Par1 where
  liftEq eq = \(Par1 a) (Par1 a') -> eq a a'

-- | @since base-4.21.0.0
instance Ord1 Par1 where
  liftCompare cmp = \(Par1 a) (Par1 a') -> cmp a a'

-- | @since base-4.21.0.0
instance Show1 Par1 where
  liftShowsPrec sp _ d = \(Par1 { unPar1 = a }) ->
    showsSingleFieldRecordWith sp "Par1" "unPar1" d a

-- | @since base-4.21.0.0
instance Read1 Par1 where
  liftReadPrec rp _ =
    readsSingleFieldRecordWith rp "Par1" "unPar1" Par1

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance Eq1 f => Eq1 (Rec1 f) where
  liftEq eq = \(Rec1 a) (Rec1 a') -> liftEq eq a a'

-- | @since base-4.21.0.0
instance Ord1 f => Ord1 (Rec1 f) where
  liftCompare cmp = \(Rec1 a) (Rec1 a') -> liftCompare cmp a a'

-- | @since base-4.21.0.0
instance Show1 f => Show1 (Rec1 f) where
  liftShowsPrec sp sl d = \(Rec1 { unRec1 = a }) ->
    showsSingleFieldRecordWith (liftShowsPrec sp sl) "Rec1" "unRec1" d a

-- | @since base-4.21.0.0
instance Read1 f => Read1 (Rec1 f) where
  liftReadPrec rp rl =
    readsSingleFieldRecordWith (liftReadPrec rp rl) "Rec1" "unRec1" Rec1

  liftReadListPrec   = liftReadListPrecDefault
  liftReadList       = liftReadListDefault

-- | @since base-4.21.0.0
instance Eq c => Eq1 (K1 i c) where
  liftEq _ = \(K1 a) (K1 a') -> a == a'

-- | @since base-4.21.0.0
instance Ord c => Ord1 (K1 i c) where
  liftCompare _ = \(K1 a) (K1 a') -> compare a a'

-- | @since base-4.21.0.0
instance Show c => Show1 (K1 i c) where
  liftShowsPrec _ _ d = \(K1 { unK1 = a }) ->
    showsSingleFieldRecordWith showsPrec "K1" "unK1" d a

-- | @since base-4.21.0.0
instance Read c => Read1 (K1 i c) where
  liftReadPrec _ _ = readData $
    readsSingleFieldRecordWith readPrec "K1" "unK1" K1

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance Eq1 f => Eq1 (M1 i c f) where
  liftEq eq = \(M1 a) (M1 a') -> liftEq eq a a'

-- | @since base-4.21.0.0
instance Ord1 f => Ord1 (M1 i c f) where
  liftCompare cmp = \(M1 a) (M1 a') -> liftCompare cmp a a'

-- | @since base-4.21.0.0
instance Show1 f => Show1 (M1 i c f) where
  liftShowsPrec sp sl d = \(M1 { unM1 = a }) ->
    showsSingleFieldRecordWith (liftShowsPrec sp sl) "M1" "unM1" d a

-- | @since base-4.21.0.0
instance Read1 f => Read1 (M1 i c f) where
  liftReadPrec rp rl = readData $
    readsSingleFieldRecordWith (liftReadPrec rp rl) "M1" "unM1" M1

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance (Eq1 f, Eq1 g) => Eq1 (f :+: g) where
  liftEq eq = \lhs rhs -> case (lhs, rhs) of
    (L1 a, L1 a') -> liftEq eq a a'
    (R1 b, R1 b') -> liftEq eq b b'
    _           -> False

-- | @since base-4.21.0.0
instance (Ord1 f, Ord1 g) => Ord1 (f :+: g) where
  liftCompare cmp = \lhs rhs -> case (lhs, rhs) of
    (L1 _, R1 _)  -> LT
    (R1 _, L1 _)  -> GT
    (L1 a, L1 a') -> liftCompare cmp a a'
    (R1 b, R1 b') -> liftCompare cmp b b'

-- | @since base-4.21.0.0
instance (Show1 f, Show1 g) => Show1 (f :+: g) where
  liftShowsPrec sp sl d = \x -> case x of
    L1 a -> showsUnaryWith (liftShowsPrec sp sl) "L1" d a
    R1 b -> showsUnaryWith (liftShowsPrec sp sl) "R1" d b

-- | @since base-4.21.0.0
instance (Read1 f, Read1 g) => Read1 (f :+: g) where
  liftReadPrec rp rl = readData $
    readUnaryWith (liftReadPrec rp rl) "L1" L1 <|>
    readUnaryWith (liftReadPrec rp rl) "R1" R1

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance (Eq1 f, Eq1 g) => Eq1 (f :*: g) where
  liftEq eq = \(f :*: g) (f' :*: g') -> liftEq eq f f' && liftEq eq g g'

-- | @since base-4.21.0.0
instance (Ord1 f, Ord1 g) => Ord1 (f :*: g) where
  liftCompare cmp = \(f :*: g) (f' :*: g') -> liftCompare cmp f f' <> liftCompare cmp g g'

-- | @since base-4.21.0.0
instance (Show1 f, Show1 g) => Show1 (f :*: g) where
  liftShowsPrec sp sl d = \(a :*: b) ->
    showsBinaryOpWith
      (liftShowsPrec sp sl)
      (liftShowsPrec sp sl)
      7
      ":*:"
      d
      a
      b

-- | @since base-4.21.0.0
instance (Read1 f, Read1 g) => Read1 (f :*: g) where
  liftReadPrec rp rl = parens $ Text.Read.prec 6 $
    readBinaryOpWith (liftReadPrec rp rl) (liftReadPrec rp rl) ":*:" (:*:)

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance (Eq1 f, Eq1 g) => Eq1 (f :.: g) where
  liftEq eq = \(Comp1 a) (Comp1 a') -> liftEq (liftEq eq) a a'

-- | @since base-4.21.0.0
instance (Ord1 f, Ord1 g) => Ord1 (f :.: g) where
  liftCompare cmp = \(Comp1 a) (Comp1 a') -> liftCompare (liftCompare cmp) a a'

-- | @since base-4.21.0.0
instance (Show1 f, Show1 g) => Show1 (f :.: g) where
  liftShowsPrec sp sl d = \(Comp1 { unComp1 = a }) ->
    showsSingleFieldRecordWith
      (liftShowsPrec (liftShowsPrec sp sl) (liftShowList sp sl))
      "Comp1"
      "unComp1"
      d
      a

-- | @since base-4.21.0.0
instance (Read1 f, Read1 g) => Read1 (f :.: g) where
  liftReadPrec rp rl = readData $
    readsSingleFieldRecordWith
      (liftReadPrec (liftReadPrec rp rl) (liftReadListPrec rp rl))
      "Comp1"
      "unComp1"
      Comp1

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

-- | @since base-4.21.0.0
instance Eq1 UAddr where
  -- NB cannot use eqAddr# because its module isn't safe
  liftEq _ = \(UAddr a) (UAddr b) -> UAddr a == UAddr b

-- | @since base-4.21.0.0
instance Ord1 UAddr where
  liftCompare _ = \(UAddr a) (UAddr b) -> compare (UAddr a) (UAddr b)

-- | @since base-4.21.0.0
instance Show1 UAddr where
  liftShowsPrec _ _ = showsPrec

-- NB no Read1 for URec (Ptr ()) because there's no Read for Ptr.

-- | @since base-4.21.0.0
instance Eq1 UChar where
  liftEq _ = \(UChar a) (UChar b) -> UChar a == UChar b

-- | @since base-4.21.0.0
instance Ord1 UChar where
  liftCompare _ = \(UChar a) (UChar b) -> compare (UChar a) (UChar b)

-- | @since base-4.21.0.0
instance Show1 UChar where
  liftShowsPrec _ _ = showsPrec

-- | @since base-4.21.0.0
instance Eq1 UDouble where
  liftEq _ = \(UDouble a) (UDouble b) -> UDouble a == UDouble b

-- | @since base-4.21.0.0
instance Ord1 UDouble where
  liftCompare _ = \(UDouble a) (UDouble b) -> compare (UDouble a) (UDouble b)

-- | @since base-4.21.0.0
instance Show1 UDouble where
  liftShowsPrec _ _ = showsPrec

-- | @since base-4.21.0.0
instance Eq1 UFloat where
  liftEq _ = \(UFloat a) (UFloat b) -> UFloat a == UFloat b

-- | @since base-4.21.0.0
instance Ord1 UFloat where
  liftCompare _ = \(UFloat a) (UFloat b) -> compare (UFloat a) (UFloat b)

-- | @since base-4.21.0.0
instance Show1 UFloat where
  liftShowsPrec _ _ = showsPrec

-- | @since base-4.21.0.0
instance Eq1 UInt where
  liftEq _ = \(UInt a) (UInt b) -> UInt a == UInt b

-- | @since base-4.21.0.0
instance Ord1 UInt where
  liftCompare _ = \(UInt a) (UInt b) -> compare (UInt a) (UInt b)

-- | @since base-4.21.0.0
instance Show1 UInt where
  liftShowsPrec _ _ = showsPrec

-- | @since base-4.21.0.0
instance Eq1 UWord where
  liftEq _ = \(UWord a) (UWord b) -> UWord a == UWord b

-- | @since base-4.21.0.0
instance Ord1 UWord where
  liftCompare _ = \(UWord a) (UWord b) -> compare (UWord a) (UWord b)

-- | @since base-4.21.0.0
instance Show1 UWord where
  liftShowsPrec _ _ = showsPrec

showsSingleFieldRecordWith :: (Int -> a -> ShowS) -> String -> String -> Int -> a -> ShowS
showsSingleFieldRecordWith sp name field d x =
  showParen (d > GHC.Show.appPrec) $
    showString name . showString " {" . showString field . showString " = " . sp 0 x . showChar '}'

readsSingleFieldRecordWith :: ReadPrec a -> String -> String -> (a -> t) -> ReadPrec t
readsSingleFieldRecordWith rp name field cons = parens $ Text.Read.prec 11 $ do
  expectP $ Ident name
  expectP $ Punc "{"
  x <- readField field $ Text.Read.reset rp
  expectP $ Punc "}"
  pure $ cons x

showsBinaryOpWith
  :: (Int -> a -> ShowS)
  -> (Int -> b -> ShowS)
  -> Int
  -> String
  -> Int
  -> a
  -> b
  -> ShowS
showsBinaryOpWith sp1 sp2 opPrec name d x y = showParen (d >= opPrec) $
  sp1 opPrec x . showChar ' ' . showString name . showChar ' ' . sp2 opPrec y

readBinaryOpWith
  :: ReadPrec a
  -> ReadPrec b
  -> String
  -> (a -> b -> t)
  -> ReadPrec t
readBinaryOpWith rp1 rp2 name cons =
  cons <$> step rp1 <* expectP (Symbol name) <*> step rp2

data I a = I [a] (Maybe a) (I a) | J (Maybe (I a)) a
  deriving stock (Eq, Ord, Show, Read, Generic1)
  deriving (Eq1, Ord1) via (Generically1 I)

-- instance Show a => Show (I a) where
--   showsPrec = showsPrec1
--   -- liftShowsPrec sp sl d =
--   --   liftShowsPrec sp sl d . from1

instance Show1 I where
  liftShowsPrec sp sl d =
    liftShowsPrec sp sl d . from1

instance Read1 I where
  liftReadPrec rp rl = fmap to1 (liftReadPrec rp rl)

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

readEither1 :: (Read1 f, Read a) => String -> Either String (f a)
readEither1 s =
  case [ x | (x,"") <- readPrec_to_S read' minPrec s ] of
    [x] -> Right x
    []  -> Left "read1: no parse"
    _   -> Left "read1: ambiguous parse"
 where
  read' = do
    x <- readPrec1
    lift Text.ParserCombinators.ReadP.skipSpaces
    pure x

main :: IO ()
main = do
  let x, y :: I Int
      x = I [1, 2] (Just 3) (J Nothing 4)
      y = I [] Nothing (I @Int [] Nothing (J Nothing 5))
  putStrLn $ show $ compare1 x y

  putStrLn $ replicate 32 '-'
  -- putStrLn $ show $ from1 x
  let x' = showsPrec1 0 (from1 x) []
  -- putStrLn x'
  -- putStrLn $ show $ readPrec_to_S (readPrec1 @(Rep1 I) @Int) minPrec x'
  x'' <- either error pure $ readEither1 x'
  putStrLn $ show x
  putStrLn $ show x''
  putStrLn $ show $ eq1 x x''

  putStrLn $ replicate 32 '-'
  -- putStrLn $ show $ from1 y
  -- putStrLn $ showsPrec1 0 (from1 y) []
  let y' = showsPrec1 0 (from1 y) []
  -- putStrLn y'
  y'' <- either error pure $ readEither1 y'
  putStrLn $ show y
  putStrLn $ show y''
  putStrLn $ show $ eq1 y y''
  pure ()

Output:

GT
--------------------------------
I [1,2] (Just 3) (J Nothing 4)
I [1,2] (Just 3) (J Nothing 4)
True
--------------------------------
I [] Nothing (I [] Nothing (J Nothing 5))
I [] Nothing (I [] Nothing (J Nothing 5))
True
RyanGlScott commented 3 months ago

Shouldn't the Read1 and Show1 instances take the record names into account? With the way that they're currently implemented, they produce different results than the Read and Show instances. For example:

test :: IO ()
test = do
  let v = Rec1 (Just ())
  putStrLn $ showsPrec  0 v "" -- Uses Show  instance
  putStrLn $ showsPrec1 0 v "" -- Uses Show1 instance
λ> test
Rec1 {unRec1 = Just ()}
Rec1 (Just ())
RyanGlScott commented 3 months ago

Another thing that was not obvious to me until I read the code more closely is that you are adding a Show instance for UAddr, in addition to the other {Eq,Ord,Read,Show}1 instances (the latter of which are the main content of the proposal).

Is there a particular reason to add this Show instance? UAddr's existing instances are meant to emulate the ability to derive instances for data types with Addr# fields, such as data Foo = MkFoo Addr# deriving (Eq, Ord). Notably, you cannot derive a Show instance for Foo, which makes me wonder why we'd want to add a Show instance for UAddr.

sergv commented 3 months ago

I've added Show for UAddr to define Show1 for completeness so that all U* types will have Show1. Maybe that's excessive, I can remove it from the MR.

sergv commented 3 months ago

Shouldn't the Read1 and Show1 instances take the record names into account? With the way that they're currently implemented, they produce different results than the Read and Show instances. For example:

test :: IO ()
test = do
  let v = Rec1 (Just ())
  putStrLn $ showsPrec  0 v "" -- Uses Show  instance
  putStrLn $ showsPrec1 0 v "" -- Uses Show1 instance
λ> test
Rec1 {unRec1 = Just ()}
Rec1 (Just ())

It looks like it will be pretty involved to make Generic1-based Show1 instances (and especially corresponding Read1) match regular Show instances derived by Ghc. Maybe it's a good argument to not add Show1/Read1 instances at all at this point?

tomjaguarpaw commented 3 months ago

Are X1 classes even supposed to be a thing going forwards? I thought that they were supposed to be replaced by quantified constraints.

RyanGlScott commented 3 months ago

I've added Show for UAddr to define Show1 for completeness so that all U* types will have Show1. Maybe that's excessive, I can remove it from the MR.

Thinking about this some more, I suppose that if the goal is to simply define some instances for the GHC.Generics data types, rather than defining instances that perfectly mimic the behavior of deriving Show, then defining Show (and Show1) instances for UAddr would be reasonable. Still, it would be good to call out the Show instance as part of the proposal description, as this wasn't obvious to me until I read it more closely.

It looks like it will be pretty involved to make Generic1-based Show1 instances (and especially corresponding Read1) match regular Show instances derived by Ghc. Maybe it's a good argument to not add Show1/Read1 instances at all at this point?

Again, I think this depends on what we want to do. If the goal is to define a Generic1-based default implementation of Show1 that mimics how deriving Show, then we'd need very different instances than what is currently proposed. (For an existing implementation of this, one can take inspiration from transformers-compat here.) If the goal is to simply define some instances (without needing to mimic how deriving Show works), then I think what you have is almost correct, but it simply needs to take the record names into account. For example, here is how you would define the Read1 and Show1 instances for Rec1:

instance Read1 f => Read1 (Rec1 f) where
  liftReadPrec rp rl = parens $ Text.Read.prec 11 $ do
    expectP $ Ident "Rec1"
    expectP $ Punc "{"
    x <- readField "unRec1" $ Text.Read.reset $ liftReadPrec rp rl
    expectP $ Punc "}"
    pure $ Rec1 x

  liftReadListPrec  = liftReadListPrecDefault
  liftReadList      = liftReadListDefault

instance Show1 f => Show1 (Rec1 f) where
  liftShowsPrec sp sl d = \(Rec1 a) ->
    showParen (d > GHC.Show.appPrec) $
      showString "Rec1 {unRec1 = " . liftShowsPrec sp sl 0 a . showString "}"
RyanGlScott commented 3 months ago

Are X1 classes even supposed to be a thing going forwards? I thought that they were supposed to be replaced by quantified constraints.

The classes in Data.Functor.Classes are strictly more powerful than formulating them with quantified constraints. For example, Show1 f is strictly more powerful than forall a. Show a => Show (f a), as Show1's liftShowsPrec method allows you to call a custom show function on the element type (e.g., you could imagine calling something like liftShowsPrec showWithExtraDebuggingInformation). Such a thing isn't possibly with the quantified constraints formulation, as far as I am aware.

sergv commented 3 months ago

I've updated original proposal with the list of types to define instances for, the reason for omitting Read1 for unboxed basic types and the rationale to define Show for UAddr (i.e. to be able to define Show1 for it).

sergv commented 3 months ago

I have updated https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12886 with Show1 and Read1 that respect record fields.

RyanGlScott commented 3 months ago

Thanks! In that case, I think this proposal looks like a reasonable addition.

Bodigrim commented 3 months ago

Dear CLC members, any non-binding opinions on this?

As https://gitlab.haskell.org/ghc/ghc/-/issues/24312 states, at the moment Generically1 is borderline useless. Unless there are Eq1 / Ord1 instances for GHC.Generics, you cannot derive anything via Generically1 defeating its very purpose.

Ericson2314 commented 3 months ago

I would like to see the *1 classes moved out of base and into a separate library, so the Core Libraries commit is no longer responsible for them. Experimentation can happen downstream then.

If the committee is interested in such a deprecation cycle, I could advise @sergv on how to do this.

Bodigrim commented 3 months ago

@Ericson2314 if you wish to discuss removal of *1 classes from base, please raise a separate proposal. IMO it's highly unlikely that CLC will approve such move, we've been reluctant to remove even much less used parts of base, but let's not digress further here.

Ericson2314 commented 3 months ago

I don't want to derail, but I think there is some use in saying a counter-proposal. That's all.

Bodigrim commented 2 months ago

If anyone ever attempts to remove *1 classes out of base (which is very unlikely), they would have to move all their instances as well anyway, so the proposal does not meaningfully contribute to the complexity of such migration.


Dear CLC members, let's vote on the proposal to add Eq1, Ord1, Show1 and Read1 instances for fundamental representation types in GHC.Generics. The history is that base-4.17 added GHC.Generics.Generically1, which was meant to provide means for deriving Eq1, Ord1, Show1, Read1 as soon as Generic1 is available. Alas, as https://gitlab.haskell.org/ghc/ghc/-/issues/24312 explains, *1 instances for Rep1 constituents were inadverently omitted, rendering entire use case for Generically1 impossible. The proposal rectifices this matter by providing missing instances.

@tomjaguarpaw @hasufell @mixphix @velveteer @angerman @parsonsmatt


+1 from me. It would be great finally put Generically1 to work, and automatic deriving of Eq1 / Ord1 would be very handy in several of my projects.

tomjaguarpaw commented 2 months ago

+1


This seems sensible. I'm not actually happy with the implementations for U1 (I think they should be strict) and V1 (I think they should use some equivalent of absurd) but that's not the fault of this MR: it's a pre-existing issue. I don't think they should make any changes in this regard, but I do want to note it.

hasufell commented 2 months ago

+1

velveteer commented 2 months ago

+1

Bodigrim commented 2 months ago

Thanks all, that's enough votes to approve.