« Math.Combinatorics.Multiset and Sawada's algorithm » Functional pearl on combinatorial species

Haskell anti-pattern: incremental ad-hoc parameter abstraction

Posted on April 3, 2010
Tagged , , , , ,

Recently I’ve found myself doing the following very ugly thing. Perhaps you’ve unwittingly done it too—so I thought I’d share the problem and its solution.

Suppose I’ve written a function foo:

> foo :: Int -> Result
> foo n = ... n ...

Who knows what Result is; that’s not the point. Everything is going fine until I suddenly realize that occasionally I would like to be able to control the number of wibbles! Well, every good programmer knows the answer to this: abstract out the number of wibbles as a parameter.

> foo :: Int -> Int -> Result
> foo numWibbles n = ... n ... numWibbles ...

But this isn’t quite what I want. For one thing, I’ve already used foo in a bunch of places in my code, and it would be annoying to go back and change them all (even with refactoring support). What’s more, most of the time I only want one wibble. So I end up doing something like this:

> foo' :: Int -> Int -> Result
> foo' numWibbles n = ... n ... numWibbles ...
> 
> foo = foo' 1

Great! Now all my old code still works, and I can use foo’ whenever I want the extra control over the number of wibbles.

Well, this may seem great, but it’s a slippery slope straight to code hell. What happens when I realize that I also want to be able to specify whether the wibbles should be furbled or not? Well, I could do this:

> foo'' :: Bool -> Int -> Int -> Result
> foo'' wibblesShouldBeFurbled numWibbles n = ... 
> 
> foo' = foo'' False
> 
> foo = foo' 1

Yes, all my old code still works and I can now succesfully control the furblization if I so desire. But at what cost? First of all, this is just… well, ugly. Good luck trying to remember what foo’‘ does and what arguments it takes. And what if I want to furble exactly one wibble? Well, I’m stuck using foo’’ True 1 because I can’t control the furblization without giving the number of wibbles explicitly.

Yes, I have actually done things like this. In fact, this problem is quite apparent in the currently released version of my diagrams library. For example:

You get the idea. So, what’s the solution? What I really want (which you may have figured out by this point) is optional, named arguments. But Haskell doesn’t have either! What to do?

I finally came up with an idea the other day… but then with a little Googling discovered that others have already thought of it. I’ve probably even read about it before, but I guess I didn’t need it back then so it didn’t stick!

Here’s the idea, as explained by Neil Mitchell: put the optional arguments to a function in a custom data structure using record syntax, and declare a record with all the default arguments. Then we can call our function using the default record, overriding whichever fields we want using record update syntax. Of course, it’s still annoying to have to remember which default record of arguments goes with which function; but the icing on the cake is that we can use a type class to provide the default arguments automatically. There’s already a suitable type class on Hackage in the data-default package.

So now my code looks something like this:

> data FooOptions = FooOptions 
>                   { wibblesShouldBeFurbled :: Bool
>                   , numWibbles :: Int
>                   }
> 
> instance Default FooOptions where
>   def = FooOptions False 1
> 
> ... foo def { wibblesShouldBeFurbled = True } ...

Nice. It might even be cool to define with as a synonym for def, to allow the natural-sounding

> ... foo with { numWibbles = 4 } ...

Of course, this isn’t perfect; if Haskell had real records it might be a bit nicer. For one thing this tends to result in a bit of namespace pollution: I can’t have another function which also takes an option called numWibbles since it will clash with the one in FooOptions! But this is still a giant improvement over the code I used to write.