Proposal: deriving ShallowEq?

Hello, I often find it useful to determine whether two objects are using the same constructor, without worrying about the constructors' arguments. An example, using some arbitrary data type "Thingo":
class ShallowEq a where shallowEq :: a -> a -> Bool
data Thingo a b = TOne a | TTwo a b Int Char Float | TThree Int Char b b
(TOne 23) `shallowEq` TOne{} True
(TThree 5 'c' True False) `shallowEq` TTwo{} False
-- Having some sort of generic shallowEq operator reduces the need for a host of predicates such as: (this one from Data.Maybe)
isJust x = case x of Just {} -> True _ -> False
.. which is an approach that is obviously going to be tedious when the size of the data type becomes large. -- There is way to hack together a partial implementation of the ShallowEq class within GHC, but it leaves much to be desired:
instance Show a => ShallowEq a where (=@=) a b = (head $ words $ show a) == (head $ words $ show b)
Notice that in the example the term "TTwo{}" is a partially constructed record. The implementation relies on laziniess to avoid trying to show the missing fields (which would fail). -- Questions: 1) Does anyone know a better/existing way to implement ShallowEq that doesn't involve enumerating all the constructors in the data type? 2) If not, can anyone think of reasons why it wouldn't be a good idea for GHC to derive ShallowEq (by expanding said enumeration)?

Ben,
I often find it useful to determine whether two objects are using the same constructor, without worrying about the constructors' arguments.
In Generic Haskell, you can define shallowEq, well ;), generically: shallowEq {| a :: * |} :: (shallowEq {| a |}) => a -> a -> Bool shallowEq {| Unit |} Unit Unit = True shallowEq {| Sum a b |} (Inl _) (Inl _) = True shallowEq {| Sum a b |} (Inr _) (Inr _) = True shallowEq {| Sum a b |} _ _ = False shallowEq {| Prod a b |} (_ :*: _) (_ :*: _) = True shallowEq {| Int |} n1 n2 = n1 == n2 shallowEq {| Char |} c1 c2 = c1 == c2 There are some more lightweight variations of this style of programming that can be embedded in Haskell, but they require some additional effort per data type. I'm not sure how this can be done with the Scrap Your Boilerplate approach, i.e., I have not give it too much thought yet, but I'm sure something can be done there too. Regards, Stefan

On Tue, 2005-07-19 at 17:01 +1000, Ben Lippmeier wrote:
Hello,
I often find it useful to determine whether two objects are using the same constructor, without worrying about the constructors' arguments.
[snip]
Having some sort of generic shallowEq operator reduces the need for a host of predicates such as: (this one from Data.Maybe)
isJust x = case x of Just {} -> True _ -> False
.. which is an approach that is obviously going to be tedious when the size of the data type becomes large.
-- There is way to hack together a partial implementation of the ShallowEq class within GHC, but it leaves much to be desired:
instance Show a => ShallowEq a where (=@=) a b = (head $ words $ show a) == (head $ words $ show b)
Ouch!
Questions: 1) Does anyone know a better/existing way to implement ShallowEq that doesn't involve enumerating all the constructors in the data type?
2) If not, can anyone think of reasons why it wouldn't be a good idea for GHC to derive ShallowEq (by expanding said enumeration)?
DriFT comes to mind: http://repetae.net/john/computer/haskell/DrIFT/ it already supplies some query operators that might make shallowEq redundant. Cheers, Bernie.

On Tue, 19 Jul 2005, Ben Lippmeier wrote:
An example, using some arbitrary data type "Thingo":
class ShallowEq a where shallowEq :: a -> a -> Bool
data Thingo a b = TOne a | TTwo a b Int Char Float | TThree Int Char b b
Questions: 1) Does anyone know a better/existing way to implement ShallowEq that doesn't involve enumerating all the constructors in the data type?
A more general approach are projection functions like getTOne :: Thingo a b -> Maybe a getTOne (TOne x) = Just x getTOne _ = Nothing Then you can map the values to be compared into a Maybe and you need only a shallowEq for Maybe.

Hello Ben, Tuesday, July 19, 2005, 11:01:32 AM, you wrote: BL> I often find it useful to determine whether two objects are using the BL> same constructor, without worrying about the constructors' arguments. BL> There is way to hack together a partial implementation of the ShallowEq BL> class within GHC, but it leaves much to be desired: BL> > instance Show a => ShallowEq a where BL> > (=@=) a b BL> > = (head $ words $ show a) == (head $ words $ show b) reading GHC sources is always very interesting :) that is from GHC/Base.hs : %********************************************************* %* * \subsection{@getTag@} %* * %********************************************************* Returns the 'tag' of a constructor application; this function is used by the deriving code for Eq, Ord and Enum. The primitive dataToTag# requires an evaluated constructor application as its argument, so we provide getTag as a wrapper that performs the evaluation before calling dataToTag#. We could have dataToTag# evaluate its argument, but we prefer to do it this way because (a) dataToTag# can be an inline primop if it doesn't need to do any evaluation, and (b) we want to expose the evaluation to the simplifier, because it might be possible to eliminate the evaluation in the case when the argument is already known to be evaluated. \begin{code} {-# INLINE getTag #-} getTag :: a -> Int# getTag x = x `seq` dataToTag# x \end{code} -- Best regards, Bulat mailto:bulatz@HotPOP.com

Bulat Ziganshin wrote:
reading GHC sources is always very interesting :) that is from GHC/Base.hs :
getTag :: a -> Int# getTag x = x `seq` dataToTag# x
! This is just what I was looking for, thankyou. My shallowEq function is now simply: shallowEq :: a -> a -> Bool shallowEq a b = getTag a ==# getTag b My project is already totally reliant on GHC, and this will save me the heartache of hacking DrIFT (which I was in the process of setting up when I saw this mail) into my makefile. Portability be damned! Ben.

On Tue, Jul 19, 2005 at 08:16:35PM +1000, Ben Lippmeier wrote:
Bulat Ziganshin wrote:
reading GHC sources is always very interesting :) that is from GHC/Base.hs :
getTag :: a -> Int# getTag x = x `seq` dataToTag# x
! This is just what I was looking for, thankyou.
My shallowEq function is now simply:
shallowEq :: a -> a -> Bool shallowEq a b = getTag a ==# getTag b
My project is already totally reliant on GHC, and this will save me the heartache of hacking DrIFT (which I was in the process of setting up when I saw this mail) into my makefile.
Portability be damned!
Ben.
You might increase portability a bit by using import Data.Generics shallowEq :: Data a => a -> a -> Bool shallowEq x y = toConstr x == toConstr y it does introduce a dependency on Data though Groeten, Remi -- Nobody can be exactly like me. Even I have trouble doing it.
participants (6)
-
Ben Lippmeier
-
Bernard Pope
-
Bulat Ziganshin
-
Henning Thielemann
-
Remi Turk
-
Stefan Holdermans