Tuesday, August 27, 2013

Program relativity: F = d*c^2

Your home and your bank account: one of these is an asset, and one is money. Enter the real estate market, and they become interchangeable, with substantial effort.

Matter and energy: they're the same at some level. If it were easy to go back and forth, we'd all move around at the speed of light.

Code and data: one of these we expect to vary with every run of our program. Perhaps if treat them as the same, if we pass code around and massage it the way we do data, then our programs can change faster.

If you're like "Code is data, OK yeah, it's bytes in a text file," then you're selling your house brick by brick. We pass code around at a conceptual level in functional programming. We work with operations; values feel like the special case.

Here's a tiny example I hit at work today.

Say we have a database, a list of groups, and a countActiveUsers method on the GroupOperations object. We could write that method to operate on the database, using the list of groups, to return the count all the currently-online users in those groups.

def countUsers(groups: Set[Group], db: DB) : Int

Now say I'm starting up a StatusUpdateActor, and I want to give it a way to count the users. The easiest, crappiest way is to pass in all three objects:

new StatusUpdateActor(db, groups, groupOperations)

A cleaner way is to create a closure and pass that in:

val countUsersOperation: () => Int = { groupOperations.countUsers(groups, db) }
new StatusUpdateActor(countUsersOperation)

It can be cleaner still. Consider - what is the GroupOperations class's job? It knows how to do stuff with groups. Knowing how to do stuff doesn't mean you're the one to do it all the time. What if countUsers returned an operation instead of performing an operation?

def countUsers(groups: Set[Group], db: DB) : () => Int

Then we wouldn't have to wrap it in a closure to pass it in to StatusUpdateActor. It's right there, an operation. If we want to run it right now, we can. If we want to pass it around, we can.

Better yet, what if I haven't decided what db to use? Why should I have to provide a DB before getting this operation?

def countUsers(groups: Set[Group]): DB => Int

Now I can get an operation on a DB that returns an Int. I can give it the db right away, or maybe StatusUpdateActor gets a db from a different source, and we want to give it the operation directly.

new StatusUpdateActor( dbSource, groupOperations.countUsers(groups) )

Moving from OO toward FP, I see application no longer as the whole point of a function, and more as the final step in a series of manipulations. Why force a caller to wrap your method in a closure sometimes? Return the operation to begin with, and let the caller apply it when they darn well please.

If you like to use efficiency as an excuse, then perhaps countUsers performs a set operation on the groups and constructs a statement before touching the db. Do all this once and return an operation that uses the statement value once a DB passed is in.

When you perform an operation, you give a man a fish. When you return an operation, you teach him how to fish.

This is one tiny example of focusing on operations. It treats code as data at a high level: here's a function, you can do a few things to it, like supply some or all of its arguments or give it to someone who might call it later. It's moving the mass of our program around at the speed of data. It's a liquid market for our program assets.

People have predicted for the last several years that functional programming is a fad -- sorry dudes, this is no speculative bubble. Buy in, because prices are only going up.

Would it help if I showed code for the methods declared above? 
And declarations for the called ones? 
I thought it might be more distracting than useful.

No comments:

Post a Comment