I did take a look at using structural records. For example:
type Player = Sprite {} -- NOTE: indistinguishable from Bomb and Shot
type Bomb = Sprite {}
type Shot = Sprite {}
type Explosion = Sprite { timeToLive : Float }
-- Does not work. Needs a fixed 'a' to fill in 'Sprite a'
spriteList : [Sprite]
Unfortunately this does not allow me to make a list of sprites in general. I would have to make a new list for every new kind of sprite and process them separately in the main game loop. This is reasonable for a small number of sprite types (≤5) but not a lot (≥10).
I looked at using an algebraic type to get the same effect. For example:
type Sprite = { x : Float,
y : Float,
d : SpriteData }
data SpriteData = Player
| Bomb
| Shot
| Explosion { timeToLive : Float }
spriteList : [Sprite] -- okay
This actually seems to work, at the cost of complicating matching somewhat when processing the list of sprites.
However both of the prior 2 approaches eliminate the "sprite type" as a first-class entity, which was very useful in the original program. In the original program I could navigate from a sprite to its sprite type, and from there to attributes about the sprite type (principally the associated image's name and dimensions).
I could duplicate the "sprite type" information in each sprite but that just feels like a waste of space.