General musings on programming languages, and Java.

Thursday, June 26, 2008

Programming in a natural order

Whenever I'm doing the same thing over and over, a little background thread in my brain starts to wonder if there's a better way. Sometimes I tell it to shut up and let me code, other times I listen. In this case it took me a long time to listen, so it must have been pretty persistent. The first time I noticed it was while writing some lisp. Here goes the usual overly trivial example:

I write (+ 3 4) then realise I want to multiply the result by 5. I have to go back to the start of the expression and change it to (* (+ 3 4) 5). I could easily blame lisp's prefix syntax and maybe define some reader macro so I can write something like (+ 3 4) >> (* 5). But that would solve one case and not really fix the overall problem. Here's the obligatory less trivial example:

I write (+ 3 x) then realise that x needs to be a lambda parameter, so I go back and write (lambda (x) (+ 3 x)). I think it's difficult to make the above >> reader macro work with this.
(+ 3 x) >> (lambda (x)) might work, but it's starting to get hard to read. It doesn't seem as natural as it did for the previous case.

But, as I said, it took me a long time to take notice of this background thread, and in fact when I did I was writing Scala, not lisp. When in lisp I just wrote the code anyway, and it didn't harm me noticably. I never wrote the above reader macro, and as I've never written one, chances are it's unwritable anyway.

The case when writing Scala was where I was looking at an expression, then realised that one of its inputs would be a collection, not a single value. A closer-to-reality example - you have a Client, and a Client has a manager, the contact there that you speak with. So you have:
def sendSpam(client: Client) = emailer.send(marketingTripe, client.manager.emailAddress). Then later you change Client.manager to Client.managers, and change its type from Manager to Iterable[Manager]. Now the emailer protests (at compile time), so you have some options:

1. Change the emailer to make it accept an Iterable[Client]. This is actually quite reasonable, and what I did in the real code this mimics.

2. Change the code to:
client.managers foreach (manager => emailer.send(marketingTripe, manager.emailAddress)).

3. Listen to the background thread in your brain, and blog about an automatic transform from the original version to option 2. Today I'm choosing option 3, as you can tell. To avoid ambiguity, I'll introduce some syntax to put around 'managers':
emailer.send(marketingTripe, ^(managers).emailAddress) will be transformed to the code from option 2 above (though perhaps without a real variable name).

At this point, you might be seriously glad that I am not a committer on the compiler for the language that you use every day. But I think the above might actually be a good idea, and might even be a better syntax for a map with a lambda than what we're used to. The reasoning is: it's preferable to write code in a natural order, rather than the order that the language forces you to.

6 comments:

Anonymous said...

It's really not *too* bad, switching between Client and Iterable[Client] in your example, especially if you use underscore notation:

emailer.send(marketingTripe, client.manager.emailAddress)

==>

managers foreach { emailer.send(marketingTripe, _.emailAddress) }

I'll agree with your overall point though: there have been many times where I have had to perform such a transform and not had a convenient way to do it. Unfortunately, I think this is a natural consequence of an unambiguous language. There will always be *some* situations where it is more difficult to alter one situation than another.

Ricky Clarkson said...

Actually your modification doesn't work, because _ makes the second parameter of emailer.send be a lambda, rather than making the call to emailer.send be a lambda.

lumpynose said...

"The reasoning is: it's preferable to write code in a natural order, rather than the order that the language forces you to."

That might be true if you're working in total isolation but code is more often than not written to be read by other people; your team members, people who come after you and have to support and modify your code, etc. And not everyone will agree on what the 'natural order' is for things. I would hate to work on a team where people go off and do things their own way.

Ricky Clarkson said...

That's one reason I blogged this idea; to see whether other people find it natural or contrived.

I think this issue is quite orthogonal to how code within a project works though; presumably you'd have rules such as "no fewer than 10 lines per method", "name everything you can possibly name", etc., that aim to force explicitness in code, to such an extent that Java seems like a good programming language.

michaelw said...

Your >> notation reminds a little of train wreck style OO (footnote 2).

Here's an attempt on option 2: how do you like redshank-enclose-form-with-lambda?

Example:
(foo x (bar y| z) qux)

C-x C-r C-l RET RET yields:
(foo x (lambda (y) (bar y| z)) qux)

GM said...

yeah, i wouldn't worry about how long it takes to change from one to the other, i'd worry about how readable it is at each stage.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.