Closed sergv closed 2 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
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 ())
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
.
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.
Shouldn't the
Read1
andShow1
instances take the record names into account? With the way that they're currently implemented, they produce different results than theRead
andShow
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?
Are X1
classes even supposed to be a thing going forwards? I thought that they were supposed to be replaced by quantified constraints.
I've added
Show
forUAddr
to defineShow1
for completeness so that allU
* types will haveShow1
. 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
-basedShow1
instances (and especially correspondingRead1
) match regularShow
instances derived by Ghc. Maybe it's a good argument to not addShow1
/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 "}"
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.
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).
I have updated https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12886 with Show1 and Read1 that respect record fields.
Thanks! In that case, I think this proposal looks like a reasonable addition.
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.
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.
@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.
I don't want to derail, but I think there is some use in saying a counter-proposal. That's all.
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.
+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.
+1
+1
Thanks all, that's enough votes to approve.
As described in https://gitlab.haskell.org/ghc/ghc/-/issues/24312, there's an easy to use
Generically1
type for derivingEq1
,Ord1
, etc instances for user datatypes that haveGeneric1
instance.Unfortunately it doesn't work because
Generically1
defers work toEq1
,Ord1
, etc instances forM1
,K1
,:+:
,:*:
, etc types fromGHC.Generics
.The proposal is to add missing instances to make deriving with
Generically1
usable. Specifically, addEq1
,Ord1
,Show1
andRead1
forV1
,U1
,Par1
,Rec1
,K1
,M1
,:+:
,:*:
,:.:
,URec
,UAddr
,UChar
,UDouble
,UFloat
,UInt
, andUWord
.Since unboxed types don't have regular
Read
instance, it's proposed to omitRead1
forUAddr
,UChar
,UDouble
,UFloat
,UInt
, andUWord
.Note that
UAddr
does not have regularShow
instance so it's proposed to add it as well becauseShow1
requires it. Suggested implementation is to reuseShow
instance forPtr
which known how to showAddr#
thatUAddr
has inside.