I have an algebraic data type with some constructors that hold comparable values, and some constructors that don't. I wrote some comparison functions that work like the standard (==) and (/=) operators, but return Nothing for comparisons that don't make sense:
data Variant = IntValue Int
| FloatValue Float
| NoValue
equal :: Variant -> Variant -> Maybe Bool
equal (IntValue a) (IntValue b) = Just (a == b)
equal (FloatValue a) (FloatValue b) = Just (a == b)
equal _ _ = Nothing
unequal :: Variant -> Variant -> Maybe Bool
unequal (IntValue a) (IntValue b) = Just (a /= b)
unequal (FloatValue a) (FloatValue b) = Just (a /= b)
unequal _ _ = Nothing
That works, but the repetition is unwieldy — especially since I actually have more Variant constructors and more comparison functions.
I thought I could factor out the repetition into a helper function that's parameterized on the comparison function:
helper :: (Eq a) => (a -> a -> Bool) -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing
equal' :: Variant -> Variant -> Maybe Bool
equal' = helper (==)
unequal' :: Variant -> Variant -> Maybe Bool
unequal' = helper (/=)
but that doesn't work because the type variable a apparently can't bind to both Int and Float at the same time in the definition of helper; GHC binds it to Float and then complains about a type mismatch on the line that handles IntValue.
A function like (==) is polymorphic when used directly; is there a way to pass it to another function and have it remain polymorphic?