Application and Composition

Haskell‘s notorious learning curve isn’t just down to the apparent need to learn advanced Category Theory before you can cogently browse the documentation. It’s also that you’re hit with a syntax that’s really unfamiliar – at least if you’re coming from the more "mainstream" languages like I am (Python, C++, Coffeescript). In particular, you’re hit with a lot of operators that appear to do similar things. The differences are subtle to me, night and day to people who actually know what they’re doing.

In particular, the difference between (.) and ($) wasn’t immediately obvious to me. They both seem to do a kind of function application.

Of course, it’s all in the name. (.) is composition, and ($) is application. Moreover, ($) has a very different precedence ranking to (.) It’s much lower on the food chain – essentially always last to go.

It’s easy to see where we newcomers get confused. After all, isn’t function application just passing an argument to a function? And isn’t function composition achieved by the same method? Aren’t these two things essentially equivalent, really?

And really, yes they are.

Nevertheless, you do run across situations where one is more appropriate than the other.

Consider a situation I was in today. I’m learning the Time package, and one of the things I was playing around with was adding to a localized time. Annoyingly, Data.Time doesn’t really give you an easy way to do this. As far as I can tell, you have to:

  1. convert the local time to UTC
  2. add the difference in seconds
  3. convert it back to your local time, which requires passing in the timezone

The last of those is the rub. Without that, we’d have an easy oneliner:

addToZonedTime offset = utcToZonedTime . addUTCTime offset . zonedTimeToUTC

So, addToZonedTime does just what I said: it takes an offset (in seconds) and returns a composed function that will take a ZonedTime (Haskell Data.Time jargon for a localized time representation object), convert it to UTC, apply the adjustment, then turn it back into its local time representation. But of course this doesn’t work – it doesn’t even compile – because utcToZonedTime is of type TimeZone -> UTCTime -> ZonedTime – which is to say, it takes a timezone as its first argument. Damn.

So the way we have to write it is:

addToZonedTime tz offset = utcToZonedTime tz . addUTCTime offset . zonedTimeToUTC

It looks like a minor change, but it’s a really annoying one, because to pass in a TimeZone representation I have to first extract it from by ZonedTime object external to the function. That just feels wrong. Passing in the offset feels natural: that changes on each call. Passing in the local time (ZonedTime) feels natural: that’s the thing I’m seeking to change, after all. But passing in the timezone is something I shouldn’t have to do – because the ZonedTime object already has that information. Passing it in separately is redundant.

I mean, how ugly is this?

addToZonedTime (zonedTimeZone localtime) (60*60*25) localtime

You can see right in the call that localtime is in there twice!

And of course, this is what where statements are for. So, how about this instead?

addToZonedTime offset localtime = utcToZonedTime tz . addUTCTime offset . zonedTimeToUTC localtime
  where tz = zonedTimeZone localtime

Much better! One problem: it doesn’t run. The error it throws says that zonedTimeToUTC is applied to too many arguments – and that’s because the way this function is defined is as nothing but composed functions. The definition says that addToZoneTime plus an offset plus a localtime is the composition of:

  1. zonedTimeToUTC applied to localtime WITH
  2. addUTCTime applied to offset WITH
  3. utcToZonedTime applied to tz WHERE
  4. tz is the timezone portion of localtime

Put simply, we’ve composed all these functions together, but nothing actually triggers it to run! They have to be applied to something. Hence – ($).

This definition works:

addToZonedTime offset localtime = utcToZonedTime tz . addUTCTime offset . zonedTimeToUTC $ localtime
  where tz = zonedTimeZone localtime

So, that’s a situation where there’s a real difference. Because the precedence of $ is so low, the compiler knows that I don’t mean to compose zonedTimeToUTC applied to localtime with everything else. Now it says what I actually meant, which is to compose all that other stuff together and then apply it to localtime. The and then is made possible by a grant from the fact that $ always gets its say last. First do whatever’s on the left and right sides of $, then apply the left to the right.

And that, in a compact example, is why Haskell gives us both (.) and ($), even though they’re the same in many prominent ways. The little differences do matter.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>