Apr 28

Coming off years of C/C++ programming, my mind is organized so that I want to write functions such that most important argument comes first in the argument list. But, in a language like haskell, that’s not always the best way.

Here’s an example from just a moment ago. I was adding haddock documentation to some haskell libraries I had written, when I came across this little utility function I had written a while back:

percent_within_range val low high =
     (val-low) / (high-low)

This function takes a value, along with the highest and lowest boundaries of a range. It returns the percentage that the value is above the low of the range. So, passing in 5 0 10 returns 0.5, since 5 is 50% of the way between 0 and 10. 10 0 10 returns 1.0. get it?

Anyway, it was natural for me to pass the val first, since that’s the item I care about when I call the function. In an OO language, percent_within_range might even be a method on val! But, in haskell, this is really the opposite of what you want.

I re-wrote the function this way:

percent_within_range low high val =
       (val-low) / (high-low)

… which is just a re-ordering of the arguments. Now, if you are just calling the function, this makes literally no difference. But, in haskell, this ordering makes it easy to define curried versions of the function like this:

pct_100 = percent_within_range 0 100

… or just use it curried directly:

map (percent_within_range 0 100) lst

You can see this style of argument ordering all over haskell (and other functional languages) libraries, and doing something different is an easy way to mark yourself as an fp n00b. I’ve finally become accustomed to writing code this way, and am still finding examples of “backwards” argument lists in code I wrote long ago.

[EDIT: Since all the comments talk about 'flip' and other strategies for rearranging elements, I want to say that: yes, there are plenty of ways to rearrange arguments, if you are faced with a function that does not curry naturally for your needs. That's not the point. The point is, with some thought, you can make occasions where you need such workarounds relatively rare. After all, I've needed to use ((flip map) [1..10]), but I’ve used (map (+1)) quite a bit more often! I was pointing out that usually the best argument order for common currying is not the order that a lot of imperative/OO programmers would choose.]