1

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.

pat
  • 12,587
  • 1
  • 23
  • 52

2 Answers2

1

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:

type family GetX t
type instance GetX (a,b,c) = a

type family GetY t
type instance GetY (a,b,c) = b

type family GetZ t
type instance GetZ (a,b,c) = c
ehird
  • 40,602
  • 3
  • 180
  • 182
  • Neat! Is there a way to use record syntax instead of plain tuples so that each type family can deal with only the field it is interested in, making it independent of the arity of the tuple? – pat Dec 27 '11 at 19:12
  • These aren't tuple values, they're tuple *types* — in `(1,2,3) :: (Int,Int,Int)`, the things these type functions are pattern-matching on is the `(Int,Int,Int)` part. So, no, there's no records, sorry :) – ehird Dec 27 '11 at 19:21
  • An alternate answer could be "if Haskell had record types you possibly could, but it doesn't". See also the never-ending discussion about how to improve Haskell's record system. – glaebhoerl Dec 27 '11 at 19:54
  • Well... not quite; even if you had a structural-typing-style thing where `{a = 42, b = "abc"}` has type `{a :: Int, b :: String}`, it wouldn't give you type-level record-type accessors; what you'd need is subtyping so that a type function pattern-matching on an `{a :: Int}` would work on an `{a :: Int, b :: String}`, but subtyping is a whole new thing entirely, and one that makes type inference problematic. – ehird Dec 27 '11 at 20:11
  • However, the new automatically-lifting-types-to-kinds stuff could work, if it was able to actually lift records. I doubt that's a very high priority, though... – ehird Dec 27 '11 at 20:12
  • Ah, that's what I meant. That kind of thing is frequently brought up as an example of something you can do in languages with record typing - which may or may not be because the most popular language that has it (as far as I know) is OCaml, which happens to also have subtyping. You may be right that these are distinct concepts, but if I remember correctly some of the record system proposals for Haskell also seek to implement it using various forms of compiler-generated type classes. (Haskell does have something quite seemingly similar to subtyping in constraints.) – glaebhoerl Dec 27 '11 at 23:00
0

This would be essentially the equivalent of a type-level lambda.

type t = (\f -> f x y z)

t Foo --> (\f -> f x y z) Foo --> Foo x y z

Except, of course, the t and Foo are flipped.

In any event, Haskell doesn't have true type-level lambdas. But you may want to check out answers to this related question: Lambda for type expressions in Haskell?

Community
  • 1
  • 1
Dan Burton
  • 53,238
  • 27
  • 117
  • 198