Names and the little details of an API matter. I’m going to pick on one small example I came across recently. In unagi-chan, a very fast lock-free queue implementation, we have the following API:
newChan :: IO (InChan a, OutChan a)
Huh, okay, they split the queue into separate read and write access. Geez, was that really necessary? If I wanted to partially apply
tryReadChan and pass around an
IO a or
IO (Maybe a), I could do that, you know. Now if I need both read and write access, you’ve forced me to pass around a pair… ugly. But okay, okay, fine, I can deal with this, this thing’s supposed to be screaming fast, I can put up with a few admittedly subjective API warts, and honestly I’m being pretty ungrateful that the people wrote this nice code and made it freely available.
Two minutes later: a type error when trying to read from an
InChan. Wait, what? Let me re-check hackage docs here… WAT?
readChan takes an
readChan :: OutChan a -> IO a
WTF!!?! What kind of idiot would—oh, wait, maybe that does make sense. I’m probably just being dense here. Calm down, Paul. Let’s think about this, do you read from the
InChan or the
OutChan? Obviously I should read from the
InChan… but AHA! From the perspective of the producer, the
InChan IS the
OutChan. But from the perspective of the consumer, it’s the reverse! This is so fucking confusing! What the hell was I even doing? I’ve forgotten by now…
(After a moment of realization). You know, it’s completely arbitrary whether the
InChan is from the perspective of a queue writer or a queue reader, so these names are just confusing as hell. Calling these two types
Woot2Chan would have been MORE INFORMATIVE, since it would not have tempted me to start writing code based on my assumed understanding of the meaning of
Out without checking the type signatures!
Not to mention, while I’m trashing this code, who decided to call those functions
writeChan? Where exactly are we writing to / reading from? Is this a stack or a queue? How about
dequeue, which is more appropriately suggestive of what’s happening?
Five minutes later, I’d settled on this: if you really insist on splitting up the queue into two types, how about these names:
newQueue :: IO (Enqueue a, Dequeue a) enqueue :: Enqueue a -> a -> IO () dequeue :: Dequeue a -> IO a
Now THIS is the sort of API I like. I can use it without switching on random parts of my brain that have nothing to do with what I’m trying to accomplish.
Also see this section of design for experts; accomodate beginners.comments powered by Disqus