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 there because it's easier than thinking up my own.
Suppose your program contains a number of types:
data Camera = Camera ...
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... | Player ... | NPC ...
Now you want to implement some basic physics, so you want them all to have a position and a velocity, say of some type Vec3
.
One way to do this is to declare a Physical
typeclass with pos
and vel
functions, and make all your types instances of it. But that means you have to modify all the types to contain two Vec3
s, which is annoying if you have a lot of nice types defined already and you just want to glue a little bit of functionality on top. The lens-based solution suggested by Chris Taylor has the same issue.
A solution that feels neater to me is to declare a new type constructor,
data Physical a = Physical a Vec3 Vec3
Then you only have to implement pos
, vel
, and a Functor
instance once, and you get to keep all your existing type declarations.
However... this doesn't compose very well. If you now want to have the ability to paint your objects blue or green or purple, you might want to do the same thing with colours:
data Coloured a = Coloured a Colour
But now if you have a Coloured Physical Camera
you have to fmap
a different number of times depending on whether you want to look at its colour or its position or its focal length. And a Coloured Physical Camera
ought to be the same thing as a Physical Coloured Camera
, but it's not. So this is not an elegant solution.
Is there a nice way to mix in different sets of functionality to types in Haskell? A simple solution that works in plain old Haskell without language extensions or a lot of boilerplate would be ideal, but I'm also open to learning any of the lens-related libraries if that's really the best way to approach the problem.
(This older question on mixins-style code reuse seems related, but I'm afraid I don't fully understand the question or the accepted solution.)