I came across this question on modeling inheritance in Haskell and it reminded me that I have a little more complicated version of the same problem. I\'ll adopt the example from
Though I still suspect we should think about the whole think about the whole thing another way, less OO-inspired, here's another possible solution. I shall keep to the Monsters example, though a 2D graphics program seems indeed a better example.
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, DeriveFunctor, FlexibleContexts #-}
import Control.Monad.Identity
class (Functor f, Functor (PropT f p)) => AttachProp f p where
type PropT f p :: * -> *
attachProp :: p -> f o -> PropT f p o
detachProp :: PropT f p o -> (p, f o)
fmapProp :: (AttachProp f p, AttachProp f p')
=> f o -- dummy parameter (unevaluated), because type-functions aren't injective
-> (p -> p') -> PropT f p o -> PropT f p' o
fmapProp q f pt = let (p, fo) = detachProp pt
in attachProp (f p) $ fo `asTypeOf` q
data R3Phys = R3Phys { position, momentum :: Vec3 }
data Colour = Colour
data Physical a = Physical R3Phys a deriving (Functor)
data Coloured a = Coloured Colour a deriving (Functor)
data PhysColoured a = PhysColoured Colour R3Phys a deriving (Functor)
instance AttachProp Identity R3Phys where
type PropT Identity R3Phys = Physical
attachProp rp = Physical rp . runIdentity
detachProp (Physical rp o) = (rp, Identity o)
instance AttachProp Identity Colour where
type PropT Identity Colour = Coloured
attachProp c = Coloured c . runIdentity
detachProp (Coloured c o) = (c, Identity o)
instance AttachProp Coloured R3Phys where
type PropT Coloured R3Phys = PhysColoured
attachProp rp (Coloured c o) = PhysColoured c rp o
detachProp (PhysColoured c rp o) = (rp, Coloured c o)
instance AttachProp Physical Colour where
type PropT Physical Colour = PhysColoured
attachProp c (Physical rp o) = PhysColoured c rp o
detachProp (PhysColoured c rp o) = (c, Physical rp o)
Note that PropT (PropT Identity R3Phys) Colour a
and PropT (PropT Identity Colour) R3Phys a
are the same type, namely PhysColoured a
. Of course, we need again O (n²) instances for n mixins. Could easily be done with Template Haskell, though obviously you should think twice if you want that.