Assumptions belong in types
I'm writing a piece of highly situated software to log in to a crappy website.
This website asks for three random characters of my password, which I don't know, because I use a password manager. So, for example, when I log in they might ask for characters 3, 5, and 11 of my password.
I don't know why they picked "three" as the magic number of characters, and I don't know whether they are going to change it. But because I'm not writing a general library, and because it gets the job done, I'm going to assume 3.
My first cut of this code stored the three requested positions in a list:
[3, 5, 11], and then accessed each element of this list as needed:
positions !! 0,
positions !! 1,
positions !! 2. This is fine.
However, because scraping is a bit of a dark art, and because websites can
return different things at times, what happened was that the parsing code
could merrily return an empty list of positions, and then the code that used
these positions would blow up with
Prelude.!!: index too large.
A different post would go on about partial functions. This isn't that post. For this highly situated software, it's OK if things blow up because of unhandled cases. The problem is it's blowing up in the wrong place. The bit that actually failed was the parsing, but that failure only manifested itself in the code that was assuming that there were 3 positions.
The solution is to move the assumption from the code to the type. So we change
positions :: [Int] to
positions :: (Int, Int, Int). This makes it
impossible for the parser to construct a wrong value, which forces the failure
into the parser.
In some ways this is just a riff on "make illegal states unrepresentable". The insight for me is that "illegal" carries connotations of "bad", "wrong", "immoral", whereas the advice holds for "against the (arbitrary) rules" or "out of bounds". It's "illegal" in the sense that picking up a football and carrying it toward the goal is illegal—it's just not what we're doing here. In this case, there's nothing wrong about the website asking me for 4 characters of my password (or even all of them!), I'm just assuming that they won't to save myself some time.
- it's OK to make crappy, specific assumptions that are probably wrong in some general case
- embed these assumptions in your types, so you can't construct assumption-breaking values
- please just ask me for my password