The Problem
I'm trying to write a game engine in PureScript. I'm new to it, but learning has been smooth since I've previously gone through Real World Haskell (though I haven't much experience using Haskell for "real" things, either). Anything that moves as many as possible of my runtime errors into compiletime errors, is a win in my book - but if the language proves to be overly restrictive of my ability to abstract away the problems, it can remove some of that win.
Okay, so, I'm trying to build a 2d gaming engine in PureScript over the HTML5 Canvas/context2d (Obviously purescript-canvas makes an excellent choice for this - I much prefer it over Elm's Graphics.Canvas module, as it maps so much more closely to the actual underlying JS API, and in particular gives me access to the individual pixels of the Canvas).
In my existing (unfinished, but usable) JS engine, the core functionality was that I would keep a list of "sprites" (heterogeneous, except that they all share a common class), and loop over them all to call .update(timeDelta) and .draw(context2d) methods.
The sprites all share a common interface, but would have to support fundamentally different data under the hood. One might have x/y coordinates; another (representing perhaps an environmental effect) might have a "percent complete" or other animation state.
The thing is, I just can't come up with an equivalent abstraction (to heterogeneous/shared-class lists) that does what I'd need it to do, without abusing the FFI to hack my way into very impure code.
Solutions (and their issues)
Heterogeneous Lists (duh)
Obviously, the best possible abstraction that can do the equivalent of a heterogeneous list, is a heterogeneous list.
Haskell-style
It turns out Haskell (that is, tricked-out GHC, not the official spec/report) offers exactly what I want - you can box up type information while still keeping class constraints, applying a single polymorphic function across all items in the list, without breaking type safety. This would be ideal, but alas PureScript doesn't currently allow me to express a type like:
data ShowBox = forall s. Show s => SB s
PureScript-Style (state of the art)
For PureScript, there's the purescript-exists package, which probably is intended to provide equivalent functionality to the Haskell solution immediatly above, and would let me—not hide, but remove—type information, and put it back in again. This would let me have a heterogeneous list, but at the expense of completely breaking type safety.
More to the point, I don't think i could make it work to my satisfaction, because even if I have a list of [Exists f], I can't just extract/re-add the type as a generic forall a. (Draw a) => a—I have to know the actual type I'm reinstating. I could include a "tag" of some sort that tells me what "real" kind of type I should be extracting, but if I'm pulling those sorts of shenanigans I might as well be coding in plain JS. Which I may have to do (for the list, not necessarily the sprites contained).
All state in one massive data value
I could unify all the sprites as having the same type, by representing all the states of the individual sprites in one massive structure, passing it to each sprite's "update" implementation (still can't use class polymorphism, but I could include a mutation function for each individual sprite value as part of the type, and use that). This sucks for obvious reasons: each sprite has the freedom to mutate/update the data of other sprites. The massive data structure has to be updated globally for each new kind of sprite state I have to represent. Can't make a library of it, because everyone who uses the engine has to modify it. Might as well be JS.
Separate, homogenous state type
Or each sprite could have separate state, and all have the same state representation. This would avoid the "fingers in each others' pies" scenario, but I've still got a uniform structure I have to update with excessive knowledge of every sprite's needs, lots and lots of wasted data for those bits of the type structure not needed by every sprite. Very poor abstraction.
Represent the different data in JSON or what have you
Ew. This way basically is just using JS data and pretending it's PureScript. Have to throw out every advantage of PureScript's typing.
No abstractions
I could just treat them all as entirely unrelated types. This means that if I want to add a new sprite, I have to update the outermost draw function to add a drawThisParticularSprite, ditto for the outermost update function. Probably the worst of all possible solutions.
What I'll probably do
Assuming that I'm correct in my assessment of the abstraction choices available to me, it seems clear that I'll have to abuse the FFI one way or another to do what I need. Perhaps I'll have a unified record type like
type Sprite = { data: Data, draw: Data -> DrawEffect, update: Data -> Data }
where Data is some kludgey type-removed thing like maybe an Exists f of some kind, and
type DrawEffect = forall e. Eff (canvas :: Canvas | e) Context2D
or something. The draw and update methods would both be specific to the individual records, and both "know" the true type to extract from Data.
In the meantime, I'd probably pursue asking the PureScript dev folks about the possibility of supporting the Haskell-style existential stuff so I could get a proper, true heterogeneous list without breaking type safety. I think the main bit would be that (for the Haskell example previously linked), the ShowBox would have to store instance information of its (hidden) member, so it'd know the correct instance of Show to use, from its own override of the show function.
Plea
Can someone please confirm whether the above is accurate in terms of what my available options are currently in PureScript? I'd appreciate any corrections, and in particular if you see a better way of dealing with the issue—especially if there's one that allows me to use only "pure" code without sacrificing abstraction—please let me know!