gallais
gallais

Reputation: 12103

Checking that two values have the same head constructor

I'd like to be able to write a function which checks that two values have been built using the same head constructor. This function:

e.g. this is not satisfactory (it is linear and the catchall will invalidate the function if I add any extra constructor):

data E = A Int | B String | C

sameCons :: E -> E -> Bool
sameCons t u = case (t, u) of
  (A{}, A{}) -> True
  (B{}, B{}) -> True
  (C{}, C{}) -> True
  _          -> False

In OCaml it is possible to use unsafe functions from the Obj module to do exactly that. Can we do something similar in Haskell (a ghc-specific solution works too)?

Upvotes: 5

Views: 365

Answers (3)

gallais
gallais

Reputation: 12103

We ended up needing a datatype of constructor names anyway so here is our current solution which does not rely on Data or GHC.Generics:

data E = A Int | B String | C
data EName = A_ | B_ | C_ deriving (Eq)

eName :: E -> EName
eName e = case e of
  A{} -> A_
  B{} -> B_
  C{} -> C_

sameCons :: E -> E -> Bool
sameCons = (==) `on` eName

Upvotes: 0

Li-yao Xia
Li-yao Xia

Reputation: 33439

You can also do this with GHC.Generics, but it's more boilerplate than The Orgazoid's answer.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}

import Data.Function (on)
import GHC.Generics

class GSameCons f where
  gSameCons :: f p -> f p -> Bool

instance GSameCons f => GSameCons (D1 c f) where
  gSameCons (M1 a) (M1 b) = gSameCons a b

instance (GSameCons f, GSameCons g) => GSameCons (f :+: g) where
  gSameCons (L1 a) (L1 b) = gSameCons a b
  gSameCons (R1 a) (R1 b) = gSameCons a b
  gSameCons _ _ = False

instance GSameCons (C1 c f) where
  gSameCons _ _ = True

data E = A Int | B String | C deriving Generic

sameCons :: (GSameCons (Rep a), Generic a) => a -> a -> Bool
sameCons = gSameCons `on` from

main = do
  print (sameCons (A 1) (A 2))
  print (sameCons (B "") C)

Upvotes: 3

Benjamin Hodgson
Benjamin Hodgson

Reputation: 44634

If you're willing to derive Data then you're good to go.

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Data

data E = A Int | B String | C deriving (Typeable, Data)

sameCons :: E -> E -> Bool
sameCons x y = toConstr x == toConstr y

ghci> sameCons (A 1) (A 3)
True
ghci> sameCons (A 1) (C)
False

Upvotes: 6

Related Questions