Recently I’ve been playing around with a library called Reanimate for programmatically generating animated GIFs. Here’s an animation I made of solving the N-queens problem.
Reanimate’s a fairly recent addition to the Haskell ecosystem and I haven’t seen a lot of discussion about actually using it, so I’d like to chime in with my own experiences with the library.
The code for generating this animation is available here.
In order to give you a real sense of what it was like, more than just what a dry list of pros and cons could do, I’d like to try to relive the experience of getting up and running with the library, in the hopes that others might find it familiar.
The scene: me messing around with continuation-passing style to solve N-queens. My thoughts: wait, I bet this would make it easy to trace the steps taken to find a solution, functionally. Maybe I could make a cool animation out of displaying that process?
I’d heard of a recently-released library called Reanimate for doing exactly this kind of programmatic animation, and I’d wanted an excuse to give it a try for a while.
Naturally, since it’s Haskell, there are multiple choices for creating programmatic animations. In this case, there seem to be two: Gloss and Reanimate. Naturally, there’s no reference or guide on which one to pick, or what differentiates them. After some poking around in the Haddock, I settle on Reanimate since it seems like it has better support for rendering to files, specifically GIFs.
On to the first test: getting the most basic thing I can on-screen. A quick skim through the documentation seems promising. Individual animations are easy to define, and then I just sequence them with simple combinators like
andThen. Hope, weary with experience, flickers. If I can get the queen on screen and give it some basic movements, the rest should be just a matter of time. Create a new project, add Reanimate as a dependency. Do an initial build. Download a free-use SVG of a chess queen. Load it using
reanimate-svg. Get stuck for half an hour trying to figure out how to convert from the type that
reanimate-svg loads in to the type that the Reanimate main library expects. Copy and paste some example rendering code, put our queen image on top.
I stare at my screen. A GIF filled with nothing but white stares back. I open the queen SVG file itself to see if I haven’t been hoodwinked into downloading an empty file. It’s there. I double-check the file loading code to see if I messed something up. Nothing. Or at least, seemingly nothing. Certainly the documentation for
reanimate-svg provides no guidance here. The actual animation code itself? Fine. Yet our output animation? Still blank.
I gulp. I’m… trying to do something pretty basic, right? A glance at the sketch of how I want the final animation to look. Another glance at the list of animations I’ve specced out to implement. One last look, this time at the completely empty GIF on-screen.
Time to settle in for a very, very long ride.
I find myself simultaneously surprised and not surprised at all at how much of an endeavour this is turning out to be.
Once again, I atttempt to consult the strange runic inscriptions provided alongside the library. Symbols dance across the page, tauntingly similar to what I know, yet their meaning remains clouded. Fragments of understanding flicker in the recesses of my mind, yet it seems the information to put it all together has fled from this place, leaving it barren and devoid of use. Haskellers purport that this document is all that is needed to make use of a library. I believe it is what they refer to as “documentation.”
I think of every person who has found themself in a similar situation to my own, of their anger, their frustration, their bewilderment at resources meant to help not doing anything of the sort. Perhaps, they might think, it is their own self that is the problem. Yet even though it is not, they find themselves caught in a trap woven of self-doubt. So I sigh, I grieve, because I know that their discouragement is entirely justified, yet it doesn’t need to be so.
Bereft of guidance, I resort to throwing code incantations at the wall, in the hopes that something will become clearer. Reanimate will reveal its secrets to me. But it must exact its fair price on my sanity in return.
Slowly the symbols and inscriptions coalesce in my mind. Knowledge sears my neurons as enigmas collapse into functions and semantics. Slowly the scriptures become clearer…
Thou shalt use Cartesian coordinates. Thou shalt not use normal SVG coordinates. The
(1, 1) vector goes up and to the right, not down and to the right. The documentation had made no mention of this.
Thy canvas shall be plotted on X and Y axes of 16 and 9 length, and no larger. The documentation had made no mention of this.
(0, 0) shall be the center of thy canvas, not its top left corner.
(8, 4.5) shall be thy top-right corner, just as
(-8, -4.5) shall be thy bottom left corner. The documentation had made no mention of this.
Should thou load an external SVG file, it will be flipped vertically. The documentation had made no mention of this.
Should thou load an external SVG file, thou must scale it to fit the fixed coordinates of 16 by 9. Thou must be the one to decipher the original dimensions of the SVG and scale appropriately. The documentation had made no mention of this.
Thou shalt not attempt to draw on a canvas of aspect ratio other than 16:9.
Thou shalt render to MP4 if thou wishes for larger resolutions, not GIF.
The price has been paid. As I unravel the secrets of Reanimate, my mind unravels with them. Madness grips my fingers, seizes my mind. Code, code foreign to me, incantations not my own, spill forth into the editor. Figures emerge on the screen. At long last, a queen begins her dance upon the board.
Relief washes over me; I lean back in my chair and breathe. Just breathe. Hours of muttering obscenities and glaring a hole through the screen to get to this point. But it finally looks like this is going to work.
Modifying the continuation-passing N-queens solution to return the steps taken takes only a few minutes. Drawing the queens moving into place is only a handful of lines of code to fold over the solution steps. Highlighting and drawing crosses on conflicting queens is similarly easy, folding over a list of the collisions.
With all the work of figuring out what any of the functions in Reanimate actually do out of the way, the advantages of representing animations as actual code, declarative code, start to shine through. It really is incredibly simple to be able to reuse all the Haskell machinery to glue animations together combinatorially, to be able to abstract out common positioning and display logic, to be able to put images precisely where I want them. All told, the full animation logic ends up being maybe 50 lines in total.
After all that struggle at the beginning, I actually can’t believe how easy it turns out be to rig everything up in the end.
To make a long story short: Reanimate is almost laughably easy to work with for creating 2D animations once you figure it out, but if I had to wish some kind of misfortune to befall my enemies, being forced to figure out how to use Reanimate wouldn’t be a bad choice.
Yet that story is depressingly common in the Haskell ecosystem, isn’t it? Artificially high barriers to entry created not because interfacing with the library is complex, or because the domain is esoteric, or because it requires difficult language constructs, but simply because the code’s author couldn’t be bothered to replace their neural circuits with a newcomer’s when writing documentation.
This topic is practically a dead horse within the Haskell community at this point, and basically everyone acknowledges that the state of “soft” documentation is, in a word, lamentable. But just because everyone implicitly agrees that it’s a problem doesn’t mean that we shouldn’t keep talking about it and pushing to make it happen; it doesn’t make it somehow “not a problem.” If someone can’t even get the most basic things to work after futzing around for a whole hour, why should they have confidence that they can use this code that someone else has written to successfully solve their issues? Why should they continue to invest time into it? Why should they continue using this stupid language where nothing works and all the documentation is useless?
I write all of this as a plea.
To Haskell open-source authors: please try to put yourself in the role of someone who has never seen your codebase before when writing documentation. I know that, since you have intimate knowledge of how everything works, this can be difficult. Divio has written a far better reference on how to write good documentation than I ever could; please use it.
And to Haskell beginners: Yes, you are absolutely in the right to be annoyed or angry when libraries don’t have documentation for even the most basic of usage. But please don’t get discouraged if you find yourself confronting a library with no documentation, no examples, and no hope. If you can’t find tutorials, ask around for help. Hell, you can even ask the library author themselves. I promise that the Haskell community is friendly and willing to lend a hand. I promise that it gets better.
For anyone kind enough to read all that and still be interested in using Reanimate, the next post is a tutorial on how to make this NQueens animation from scratch.
Once again, the code to generate this animation is available here.
Have your own anecdotes of frustration dealing with Haskell’s documentation situation? Found this useful? Still have questions? Talk to me!
Before you close that tab...
Want to become an expert at Haskell, but not sure how? I get it: it's an endless stream of inscrutable concepts and words, as if you've stepped into some strange bizarro world. Where do you even start? Why does any of this matter? How deep do these rabbit holes go?
I want to help. What if you always knew exactly what the next signpost on your journey was, if you always knew exactly what to learn next? That's why I created a Roadmap to Expert for you: a checklist of everything you need to know, sorted by difficulty, broken down into individual, easily-digestible chunks. Best of all: it's free! Just sign up for my email list below.
And there's more where that came from: Only a fraction of what I write ends up on this blog. Sign up and get advice, techniques, and templates for writing real, useful programs, straight to your inbox.
Absolutely no spam, ever. I respect your email privacy. Unsubscribe anytime.