:*: is a tuple (almost)

:*: is used to combine two types in the same way a tuple does. The only difference is that :*: carries around the additional p parameter that all Generics types carry around.

data (,)   a b   = (,)    a     b
data (:*:) a b p = (:*:) (a p) (b p)

Haskell will use :*: two represent constructors with multiple parameters. These can be regular parameters or fields of a record. Types with multiple parameters are sometimes called product types, hence the multiplication symbol in :*:. Here’s two examples of product types.

data Ghost = Ghost { haunt :: Coords, opacity :: Double }
data Monster = Monster Species Odor HidingPlace

In the generics representation of a contructor with two parameters, like Ghost, those parameters land on opposite sides of a single :*:. If the constructor has three or more parameters, like Monster, then it takes multiple :*: to combine them all. The generics representation of Monster looks like this:

-- pseudo code below, because wrappers like M1 and K1 are omitted!
type instance Rep Monster = Species :*: (Odor :*: HidingPlace)

A tuple value (2, "Hi!") looks a lot like its type (Int, Text). It’s the same with :*:. A value of the zombie type, again omitting K1 and M1 wrappers, looks like this:

monster = Zombie :*: (Decay :*: UnderBed)