BXG Blog

Type Systems vs Dimensional Analysis

Today I was enjoing the puzzle of figuring out which existing Haskell functions I could compose to make a specific new function. I suddenly realized that function design in a strongly typed language has a lot in common with dimensional analysis from physics.

Dimensional analysis is really useful for solving problems where you don’t have a direct conversion between units available. For a very simple example, suppose I walk 10000 steps per day and want to know how far I walk in a year. I don’t know the conversion from steps per day to km per year, but I can build one like so:

10000 steps/day * 0.7m/step * 365 days/year * 1km/1000m = 2555km/year

Similarly, you can often solve functional programming problems by composing simple functions in the same way that these small units are composed into a bigger conversion. Today I had a list of Int and a function that produced Maybe Int. I wanted to find out if the value returned from the function was in the list. You can do something like this:

f x `elem` [Just 1, Just 2, Just 3]

It seemed inelegant to have to keep repeating Just for each value. Something like this gets you a little closer, but it still feels like I’m typing out the implementation details instead of writing the computation I want:

f x `elem` (map Just [1, 2, 3])

So then I backed up and asked how can I break down my original problem? I need a function that combines a Maybe a with [a] and returns Bool. Let me look in the Maybe monad documentation for a function that looks like that. Aha, maybe is pretty close. It looks like

maybe :: b -> (a -> b) -> Maybe a -> b

It has that extra b in there to represent the default value when Nothing is enountered, but that’s the price of generality. And then for the a -> b function, we can go from Int to Bool based on whether the argument is in the list. That function is called (`elem` [1, 2, 3]). Putting this all together, we can write:

maybeInList = maybe False (`elem` [1, 2, 3]) (f x)

If we want to check Maybe values from different sources, we can leave off the (f x) part or pass it in as a parameter. Beautiful! This is at least as clear as the original version, and it leaves the compiler and library implementor free to do fancy optimizations later.

Of course, this is all just the basic rules of function composition, but I still thought it was fun to notice the similarity to dimensional analysis. It just goes to show that the same underlying mathematical mechanisms show up everywhere.