Tuesday, August 13, 2013

Thoughts on Unvarying Variables and One-Instance Classes

Closures scare me. There, I've said it, now the cool kids won't speak to me. Which is strange, because the cool kids all seem to be into functional programming, and I see full-fledged closures as the antithesis of functional programming. Perhaps we are using different closures.

Clearly, definitions are in order, and my definition of a closure starts with it being a function that has the characteristics of an object: it can be referenced by a variable and passed as an argument to another function. But my definition doesn't stop there. Critically, a closure has unrestricted access to variables defined in its enclosing scope.

Here's an example of a closure in action (if you don't have Go installed, you can paste this into the Go Tour to run it). Everything here happens in the main thread, but I could create a goroutine to run either function, or I could pass these functions into other functions.

package main

import "fmt"

func main() {
    x := 0

    f1 := func() {
        x += 1
    }

    f2 := func() {
        x += 2
    }

    f1()
    f2()
    
    fmt.Println("x = ", x)
}

Some people will see this and say “well, I'd never use closures this way!” I'll act as devil's advocate by saying that sometimes — sometimes — using closures this way is incredibly useful. But not often. And, unfortunately, I've seen a lot of closures used in exactly that way, often unintentionally. And that's what scares me.

For a long time I thought that my fear was simply a fear of unbridled mutability. Indeed, the first version of this post (drafted several years ago but never published) started with the following:

Var, huh! Good God, y'all, what is it good for?

That, and the title, were just too good to discard with the rest of the text. If you're too young to get the reference, don't worry; it isn't really the cause of my fear.

For the last few weeks I've been discussing my fear with friends, and I've arrived at a better reason: closures create an ad hoc class, whose methods are conjoined solely by the fact that they share common variables. As a person who believes that object-oriented programming has something useful to offer, I find this deeply disturbing. It violates several principles of object-oriented design, not least being the single responsibility principle.

But worse, in my mind, is that the methods so conjoined don't have a name. They are simply actions that happen to know about the same Thing. OK, I realize that some people scorn the idea that all Things should have names. Perhaps those same people name all of their variables a1, a2 and so on. Personally, I stopped doing that when I stopped programming in BASIC.

Named classes, when used properly, provide a way to decompose a program into clearly bounded units of functionality. The methods in the class are all related in common purpose to provide that functionality. And again, used properly, names are an important part of this decomposition; if you see that a frob is getting fribbled, your attention turns immediately to the Fribbilator (or maybe the FrobManager, but definitely not the Bargulator). With closures, and their ad hoc relationships, your task becomes much harder: you might have to walk the entire codebase.

Does this mean I reject closures? No, of course not, not even closures that make use of their ability to mutate enclosing state; as I said, sometimes that's useful. But my fear — which I now name Caution — makes me ask the following question as I'm coding: “should this be a closure or a class?”

No comments: