On a number of occasions, I've encountered situations where I've wanted to be able to define multiple data types that referenced the same data constructors, without requiring wrapping and unwrapping; while lenses make traversing these sorts of nested data structures easy, I still am not completely comfortable with them, so I went looking for something a bit simpler. The solution that came to me was this:
data X = X
data Y = Y
data Z = Z
-- An algebra over X and Y
data XYAlg a = XYAlg (X -> a) (Y -> a)
class XYEval b where
evalXY :: XYAlg a -> b -> a
instance XYEval X where
evalXY (XYAlg f _) = f
instance XYEval Y where
evalXY (XYAlg _ f) = f
-- An algebra that overlaps with XYAlg in Y
data YZAlg a = YZAlg (Y -> a) (Z -> a)
class YZEval b where
evalYZ :: YZAlg a -> b -> a
instance YZEval Y where
evalYZ (YZAlg f _) = f
instance YZEval Z where
evalYZ (YZAlg _ f) = f
Now, I can write code that's constrained by XYEval or YZEval. The only downside that I can see here is that if I want to pass around mixtures of Xs, Ys and Zs that I'll have to create data wrappers that capture the typeclass instances for their evaluators via an existential:
{-# LANGUAGE ExistentialQuantification #-}
data XY a = XYEval a => XY a
data YZ a = YZEval a => YZ a
Too much effort for overlapping data constructors? I'm not sure. The one nice thing about this approach is that it allows one to work in a bit more ad-hoc of fashion than do standard data declarations, but are there other approaches that are less noisy and/or avoid the existential?