Monday, August 4, 2014

Fixing My Knowledge Gaps: Closures

Everybody has gaps in their knowledge.  I am no exception.  The longer those gaps are there, the more painful they are to admit. If you ask a friend, you fear their response is going to be "Oh my god! How did you get through calculus without knowing that addition is commutative!" Searching the web is even worse. If search the web for "addition commutative" you get thick paragraphs of impenetrable definitions.

Despite the potential for  humiliation, I recently asked my friend Greg for help bridging my most embarrassing knowledge gap.  (Not with the commutative property, obviously - everybody knows what that is.) I warned him that I was sensitive about my ignorance, but I pleaded with him, "Please be gentle, I want to understand closures."

I read the Wikipedia page on Closures.  I read two of the top Stack Overflow answers to "what are closures." None of it helped. The Stack Overflow post had a definition that said, 
"a closure is a stack-frame which is not deallocated when the function returns (as if a 'stack-frame' were malloc'ed instead of being on the stack!)"

Maybe it's just me, but when I read things like that, my eyes glaze over, and I understand nothing.

They also had worked examples.  When I read them, I could follow them, but I never felt enlightened.  They were too toy-like and disconnected from reality. 

function startAt(x)
   function incrementBy(y)
       return x + y
   return incrementBy

variable closure1 = startAt(1)
variable closure2 = startAt(5)

And I said, "This example is stupid. It's incrementing numbers in a really fucked up way.  I thought closures were magical. I don't understand the magic, where is it?"

He said, "If you understand this, then I think you already do understand closures." 

I should have been happy, maybe, but at the moment, I felt so much frustration.  If I do understand closures, why don't I feel like I understand closures?  I feel like I never use them and other people are using them.  I don't even know how to use them in a sentence properly, can I "use a closure"? Do I "design" a closure? Do I write a closure?

At this point, we were both frustrated.  No progress had been made.  I rambled angrily about this illusive magic of closures. I almost went so far as to accuse him of not understanding closures if he didn't understand the magic.  Then he asked me,

"Other than that you don't use them. How do you know that you don't understand closures?"

I thought for a moment, and I said I listened to a lecture on JavaScript recently, and I understood everything up until the moment where they had this code.  And they explained it as being a closure:

calc = (function () {
   var num1 = 5 
   var num2 = 6 
   return { 
       set1 : function (x) { num1 = x}, 
       add : function () { 
           alert(num1 + num2) 
       } 
   } 
})() 

calc.set1(7) 
calc.add()


Greg looked at it and said, "There is some syntactic sugar here. They're making a a function and calling it as the same time.  Here is another way to write it:"

createCalc = function () { 
   var num1 = 5 
   var num2 = 6 
   return { 
       set1 : function (x) { num1 = x}, 
       add : function () { 
           alert(num1 + num2) 
       } 
   } 
}
c = createCalc() 
c.set1(7) 
c.add()

I watched him as he removed a the mysterious enclosing parentheses, and inserted the line 
'c = createCalc()'

My mind was blown. 'REALLY?' I asked, in verbal all-caps.

"Yes"

"Well, fuck. I GET THAT! createCalc() is just a function that returns a calculator."

That insecurity had been needling me for so long. I'd been sweeping it under the rug for so long, and it wasn't even about closures.  Our knowledge about our own knowledge is far from perfect. The communication frustration that Greg and I had disappeared after we used a real-world example as a conversational aid.

I calmed down a bit. Realizing that I did have a basic understanding of closures and that I wasn't totally incapable of understanding them made some of my self-respect return.

"Ok, I do understand closures at a basic level.  But I still don't see why they are special."

We talked about whether they are special.  Until "I get the simple examples.  I need a more complicated example.  An example that illustrates that closures are basically necessary, that they are exactly the right design choice for some type of problem."

"Why did they bother to use a closure in the calculator example.  You don't need one.  You could have just used a function that performs the calculation, like this:

calc = function (n, m) {
    return n + m
}
answer = calc(5, 5)

In total exasperation I said, "What would you ever realistically want a closure for?" 

Greg looked at my simple example.  "I can't think of a good example off hand. However, I can tell you the difference between the way they wrote the calculator and the way you wrote it.  They wrote a closure, and a closure has two parts: the function part and the "state" part.  Their example has some state that each calculator that gets created carries around, which is sort of 'invisible'.  Your calc function doesn't have that.

The term "invisible state" triggered something inside me. Box and pointer diagrams bubbled back into my memory.  

"Ah!" I said, "If the calculator needed to keep around some memory of what the last result was, then you'd need a closure.  And that's pretty realistic.  Calculators have a notion of the 'lastResult.' You can press '5 + 5 =', but you can also, just see that 10 is already the 'lastResult' and just say '+ 5 =' and get 15.  

I went and wrote a the state-ful calculator I had envisioned.

createCalc = function () {
    var lastResult = 0
    var setLastResult = function (x) { 
            memory = x
    }
    return {
        addToMemory : function(n){
            var ans = n + memory
            setLastResult(ans)
            return ans
        },
        add : function (n, m) {
            var ans = n + m
            setLastResult(ans)
            return ans
        }
    }
}


c = createCalc()
answer1 = c.add(5,5)              // 5 + 5 = 10
answer2 = c.addToMemory(5)        // 10 + 5 = 15

I showed my new calculator to Greg and explained:

"If you created two different calculators, they'd each have their own separate 'lastResult state.' Is this an example of a closure?"

"Yes."

And suddenly, I understood the magic.  With Greg's blessing of my example, my internal understanding of closures had transformed. It was the transubstantiation of closures, where examples turned into a concept.

Lessons Learned
  1. Examples can aid communication. Rather than get angry and frustrated in the abstract, try being angry and frustrated about an example that externalizes the issue.
  2. Make sure to articulate 'invisible' things.  People tend to assume that "what you see is all there is" (WYSIATI). When Greg explicitly pointed out that the invisible state (the enclosing environment) was a hallmark of closures, I was able to create an example that incorporated that.
  3. Realistic examples sink in better than toy examples.  They give a better sense of when you would actually want to use the concept. That contributed to my feeling of understanding closures, even though I probably did understand them in some sense before.
  4. Confusion can have multiple layers.  My insecurities about not understanding closures lead to me attributing any example containing closures that I didn't understand to be a misunderstanding of closures. 

Even though my knowledge gap was embarassing and painful to fix, I'm glad I did it.  If you're at all inclined to do so, I recommend it.

Let someone debug you.

No comments:

Post a Comment