I just flew from Tel Aviv to Los Angeles. It's a long flight, and includes significant travel time outside of the flight itself. There are two results of that: (1) I've slept something like 8 hours in the past 72, and (2) I've had a lot of time to play with random code sitting on my laptop and think about it from different perspectives. I'm going to talk about (2), and use (1) as an excuse for anything ridiculous I say. Note that this blog post is just exploring ideas, not giving any real statements.
The following two pieces of code are identical in functionality:
someFunc1 key = case lookup key someMap of Nothing -> getDefaultValue Just value -> otherFunc value someFunc2 = maybe getDefaultValue otherFunc . flip lookup someMap
Which is better? I'll start with objective facts from my own experience:
- When I was a beginner Haskeller, I would have been able to write something
- When I was a beginner Haskeller, I would have had to try and puzzle out someFunc2` for a long time if I read it, and almost certainly couldn't have written it
- Past the beginner phase, reading code like
someFunc2taught me new ways to compose Haskell code easily
- As an intermediate Haskeller, I definitely got the feeling that I should be
writing my code in
someFunc2style (point-free, avoid pattern matching, use function composition)
- I seem to receive pull requests on a regular (though not frequent) basis
rewriting code in the style of
someFunc1to look like
- I have a gut reaction that
- And yet, to this date, I think I can still parse and understand
someFunc1more easily, and also believe I will more quickly spot a bug in that style
There's quite a few "I got the feeling" statements above. Those are objective statements about my subjective observations; I'd be really intersted in whether other people have had the same kinds of observations over time, or if my experience is unique. But now, the truly subjective/exploratory part:
It seems like my progression as a Haskeller results in forcing myself to write in a harder-to-parse style to make my code shorter, to satisfy some base need for "better" code, even though by most measurements I just made, the longer/explicit/pattern matching code is in fact better.
There are certainly advantages to point-free style though, right? Some more
complicated combinators - like
foldr - result in clearer code, plus code that
generalizes to more data types than just a list (thanks to
Foldable). In some
cases (like stream fusion and other rewrite rules) point-free style may be more
efficient; I know that in conduit
await >>= maybe f g is more efficient than
To try and generalize my point beyond point-free style: we've been having some discussions about more code sharing in the web framework space recently. One point that's come up is a difference between Servant and Yesod regarding explicitness of parameters. For example, in Yesod (and I think other WAI frameworks), there's a function available inside your handler code to lookup request headers. In Servant, a dependency on such a piece of data is explicit at the type level. (There are similar issues around short-circuiting requests, and explicitness of different content representations.)
My 5-year-more-Haskell-experienced self is very much intrigued by the Servant approach. It seems more sound. And yet, I still interact regularly with less experienced Haskellers. And just this past week, this kind of need for explicit declaration of all data came up in practice for a customer, and resulted in Haskell being a harder adoption for them.
I'm feeling the same tension again. The Yesod API seems to appeal to what the
beginner would expect at a straightforward level: if you want to send a
redirect, you just call the
redirect function, and don't need to worry about
changing type signatures around. Need a request header? Ask for it. However, I
know that being explicit about these things gives great benefit (in Servant's
case, it has the ability to generate API bindings that Yesod doesn't have). But
even so, today when I write a web app in Haskell, I still like the ability to
use the "beginner-friendly" API without the explicitness.
This is where I'm stuck. There are clearly benefits to each kind of approach in these dichotomies. And I'm sure there are more dichotomies than I've listed. The kind of questions I've been pondering are:
- If I would design an API differently today than in the past, based on my larger Haskell experience, what does that mean? Is the new API better? Or have I lost touch with what's most useful, and am instead chasing an less important ideal?
- Is there an inherent tension between beginner-friendly and expert-friendly APIs for some of these things, and are the two groups best served by separating out the APIs?
- When do I give in to the voice that says "you could rewrite that to be cleaner" and when do I say "no, the simple code is better, don't complicate it in the name of cleanliness."
Sorry for the mostly unintelligible rambling. In the interest of sometimes listening to the less experienced person inside of me, I'm posting this without much review, in case I'm actually hitting on an important topic. If not, please ignore me.
And in case anyone's curious, a lot of this came up when working on
which attempts to extract the
HandlerT transformer from Yesod into something
more generally useful, and in a more modern coding style. That work (combined
with lack of sleep on a plane) seems to have opened up some existential