Let’s say I want to create a complex data structure consisting of multiple mutually recursive data types, with multiple type variables, and some functions to operate on those types:
data Foo x y z = FooA x | FooB (Bar x y z) | FooC (Foo x y z) | FooD (Baz x y z)
data Bar x y z = BarA y | BarB (Baz x y z) | BarC (Bar x y z) | BarD (Foo x y z)
data Baz x y z = BazA z | BazB (Foo x y z) | BazC (Baz x y z) | BazD (Bar x y z)
f :: Foo x y z -> Bar x y z -> Baz x y z
g :: Bar x y z -> Baz x y z -> Foo x y z
Is there a way I can bundle the x y z types, giving them a single name t, and pluck out the x, y, or z types when I actually need them? I don’t want to proliferate x y z throughout my data and function type declarations since adding an additional parameter could affect a lot of code.
i.e.
data Foo t = FooA (GetX t) | FooB (Bar t) | FooC (Foo t) | FooD (Baz t)
data Bar t = BarA (GetY t) | BarB (Baz t) | BarC (Bar t) | BarD (Foo t)
data Baz t = BazA (GetZ t) | BazB (Foo t) | BazC (Baz t) | BazD (Bar t)
f :: Foo t -> Bar t -> Baz t
g :: Bar t -> Baz t -> Foo t
I don’t know how to define the GetX, GetY, and GetZ type ‘functions’ to extract the component types from the bundle.
You can do this with type families, but it’s almost certainly overkill; it’s generally best to design your data types so that they won’t need any additional parameters in the future. Of course, that comes very close to predicting the future, but when in doubt you should probably add a parameter rather than using a concrete type 🙂
If you have a concrete example I could give more concrete advice, but generally the best thing to do here is just to keep using the parameters, or else figure out a way to abstract out the relation between the three types so that you only need one parameter that fully specifies the others (without making it just be a list of the types in question).
Still, here’s an example of how to achieve it with type families: