Closures (programming): Difference between revisions

From Helpful
Jump to navigation Jump to search
mNo edit summary
 
(8 intermediate revisions by the same user not shown)
Line 25: Line 25:
'''Example still on the fairly technical side (skip if you want something more practical)'''
'''Example still on the fairly technical side (skip if you want something more practical)'''


Consider the following pseudocode {{comment|(it's javascript, but pretend you don't know whether it supports closures)}}:
Consider the following pseudocode {{comment|(valid javascript, but pretend you don't know that, or whether it supports closures)}}:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
var add = function(a) {
var add = function(a) {
Line 49: Line 49:
On top of that, the above is apparently a language that allows you to return functions.
On top of that, the above is apparently a language that allows you to return functions.


Which means it needs to deal with the possibility that an inner function can get handed outside - as we've actually done above.
Which means it needs to deal with the possibility that a function can get handed out of the scope it was defined in. Above, we've handed that inner function outside.




What would that do to the scopes involved? In this case, after that inner function is returned and handed out, how does it find {{inlinecode|a}}?
What would that do to the scopes involved? In this case, after that inner function is returned and handed out, how does it find {{inlinecode|a}}?


{{comment|(There's a few different options, and it's an interesting exercise to work them out if you care to learn more about how scope works in different execution models)}}  
{{comment|(There's a few different ways you could argue that should work. It is an interesting exercise to work them out, if you care to learn more about how scope works in different execution models)}}  


Say, a language that allows nested function but not closures would probably consider this invalid code {{comment|(the precise reasons would vary a little with how exactly that languages thinks about scope)}}.
Say, a language that allows nested function but not closures would probably consider this invalid code {{comment|(the precise reasons would vary a little with how exactly that languages thinks about scope)}}.
Line 124: Line 124:


Say you have the code:
Say you have the code:
<code lang="javascript">
<syntaxhighlight lang="javascript">
function set_class_on_mouseover(new_class) {
function set_class_on_mouseover(new_class) {
   var divs    = document.getElementsByTagName('div');
   var divs    = document.getElementsByTagName('div');
Line 136: Line 136:
}
}
window.onload=set_class_on_mouseover('red');  
window.onload=set_class_on_mouseover('red');  
</code>
</syntaxhighlight >


set_class_on_mouseover is executed once, and puts that inner function on all divs's mouseover handler.
set_class_on_mouseover is executed once, and puts that inner function on all divs's mouseover handler.
Line 167: Line 167:


Say you do a lot of sorts on different things, and find yourself typing things like:
Say you do a lot of sorts on different things, and find yourself typing things like:
<code lang="python">
<syntaxhighlight lang="python">
matrixish.sort( lambda a,b: cmp(a[2],b[2]) )
matrixish.sort( lambda a,b: cmp(a[2],b[2]) )
</code>
</syntaxhighlight >




Instead, you can  
Instead, you can  
<code lang="python">
<syntaxhighlight lang="python">
# define this once
# define this once
def nth_lex(n):
def nth_column_lex(n):
     return lambda a,b: cmp(a[n],b[n])
     return lambda a,b: cmp(a[n],b[n])


# and use it any amount of times like:
# and use it any amount of times like:
matrixish.sort( nth_lex(2) )
matrixish.sort( nth_column_lex(2) )
</code>
</syntaxhighlight >




Line 187: Line 187:


You can use python closures to support syntactic sugar such as:
You can use python closures to support syntactic sugar such as:
<code lang="python">
<syntaxhighlight lang="python">
def paid_more_than(amount):
def paid_more_than(amount):
     amount += 10 # ...cheapskate hack
     amount += 10 # ...cheapskate hack
     return lambda item: item['salary'] > amount
     return lambda item: item['salary'] > amount


fair      = paid_more_than( 140 )
fair      = paid_more_than( 140 )
expensive = paid_more_than( 160 )
expensive = paid_more_than( 160 )
# fair and expensive  are functions that return true or false, so can be used in filter()


employees=[ {'salary':150}, {'salary':170,'isManager':True}, {'salary':120}  ]
employees=[ {'salary':150}, {'salary':170,'isManager':True}, {'salary':120}  ]
print filter( fair,      employees )
printfilter( fair,      employees )
print filter( expensive, employees )
printfilter( expensive, employees )
</code>
</syntaxhighlight >


<tt>fair</tt> and <tt>expensive</tt> are newly created functions {{comment|(here via lambda because it's brief that way)}}, and their variable 'amount' is based on the value that it had in its defining context - which is the respective executions of <tt>paid_more_than</tt>.  
<tt>fair</tt> and <tt>expensive</tt> are newly created functions {{comment|(here via lambda because it's brief that way)}}, and their variable 'amount' is based on the value that it had in its defining context - which is the respective executions of <tt>paid_more_than</tt>.  
Line 212: Line 213:


Of course, the above example is a little silly by being one-shot, as the for loop:
Of course, the above example is a little silly by being one-shot, as the for loop:
<code lang="python">
<syntaxhighlight lang="python">
amount=140
amount=140
for e in employees:
for e in employees:
     if e['salary']>amount:
     if e['salary']>amount:
         print e
         print e
</code>
</syntaxhighlight >
...is good enough.
...is good enough.


Line 232: Line 233:


Consider this python code:
Consider this python code:
<code lang="python">
<syntaxhighlight lang="python">
def foo():
def foo():
     x='a'
     x='a'
Line 239: Line 240:
     bar()
     bar()
     print x
     print x
</code>
</syntaxhighlight >


{{comment|(This isn't even a closure construction, just a local function, but it carries through to closures.)}}
{{comment|(This isn't even a closure construction, just a local function, but it carries through to closures.)}}

Latest revision as of 17:52, 11 September 2023

Some fragmented programming-related notes, not meant as introduction or tutorial

Data: Numbers in computers ·· Computer dates and times ·· Data structures

Wider abstractions: Programming language typology and glossary · Generics and templating ·· Some abstractions around programming · · Computational complexity theory notes · Synchronous, asynchronous · First-class citizen

Syntaxy abstractions: Constness · Memory aliasing · Binding, assignment, and such · Closures · Context manager · Garbage collection

Sharing stuff: Communicated state and calls · Locking, data versioning, concurrency, and larger-scale computing notes

Language specific: Python notes ·· C and C++ notes · Compiling and linking ·· Lua notes

Teams and products: Programming in teams, working on larger systems, keeping code healthy · Benchmarking, performance testing, load testing, stress testing, etc. · Maintainability

More applied notes: Optimized number crunching · File polling, event notification · Webdev · GUI toolkit notes

Mechanics of duct taping software together: Automation, remote management, configuration management · Build tool notes · Installers


This is about the programming concept - not mathematical closures, physhological closure, or any other kind.


Definition

Closures are easy enough to define technically, but harder to explain usefully.


Say, Foldoc says "a closure is a data structure that holds an expression and an environment of variable bindings in which that expression is to be evaluated. The variables may be local or global."


Even if you've reread that a few times, or even already understand closures, that is still not very clear. So:

Technical side

In a lot of cases, the rough intuition for a closure is a function that remembers (part of) the scope it was defined in.

...which matters only when a function can be used in other scopes, so this is mostly a thing where functions/lambdas are first class citizens, i.e. things that can be handed around.


Example still on the fairly technical side (skip if you want something more practical)

Consider the following pseudocode (valid javascript, but pretend you don't know that, or whether it supports closures):

var add = function(a) {
  return function(b) {
    return a + b;
  }
}

//create new function
var add10 = add(10);

//test it:
add10(5);   // 15, yay


Note that a is declared in the outer function's scope (by being a parameter on it, but that doesn't really make a difference).

The inner function gets access to a because local/nested functions gets its own scope, but also to see their parent's function's scope. (which is often half the reason nested functions themselves are useful).


On top of that, the above is apparently a language that allows you to return functions.

Which means it needs to deal with the possibility that a function can get handed out of the scope it was defined in. Above, we've handed that inner function outside.


What would that do to the scopes involved? In this case, after that inner function is returned and handed out, how does it find a?

(There's a few different ways you could argue that should work. It is an interesting exercise to work them out, if you care to learn more about how scope works in different execution models)

Say, a language that allows nested function but not closures would probably consider this invalid code (the precise reasons would vary a little with how exactly that languages thinks about scope).


A fairly sensible alternative is to say something like "everything that is in scope while a function was defined, will be available when you call add from anywhere else."

In the example, that means the anonymous inner function inside add will get a reference to a.

Which means the above example is working Javascript code.


(You may notice that this is not the best example for when closures are useful. If the language supported function nesting but not closures, you could get around it just by handing a into the inner function explicitly, thereby binding it into the inner function's scope. Not the cleanest solution if you have a lot of them, but certainly workable.)


Examples

Javascript uses

Python use

Closures exist in python since 2.2.


Example: if you do a lot of sorting

One nice thing it allows is sorting an array-in-array structure by arbitrary columns, without having to hardcode for each specific case.

Say you do a lot of sorts on different things, and find yourself typing things like:

matrixish.sort( lambda a,b: cmp(a[2],b[2]) )


Instead, you can

# define this once
def nth_column_lex(n):
    return lambda a,b: cmp(a[n],b[n])

# and use it any amount of times like:
matrixish.sort( nth_column_lex(2) )


Example: if you do a lot of filtering

You can use python closures to support syntactic sugar such as:

def paid_more_than(amount):
    amount += 10 # ...cheapskate hack
    return lambda item: item['salary'] > amount

fair      = paid_more_than( 140 )  
expensive = paid_more_than( 160 )
# fair and expensive  are functions that return true or false, so can be used in filter()

employees=[ {'salary':150}, {'salary':170,'isManager':True}, {'salary':120}  ]
print(  filter( fair,      employees )  )
print(  filter( expensive, employees )  )

fair and expensive are newly created functions (here via lambda because it's brief that way), and their variable 'amount' is based on the value that it had in its defining context - which is the respective executions of paid_more_than.

This makes them independent (no side effects, e.g. no outside alterations of amount is possible).

This also means you can use them in list comprehensions and generator functions (ignore them if you don't know them yet; think of it them as list creators-and-filters):

result = [e  for e in employees  if fair(e)]

Note that fair(e) is more or less like saying paid_more_than(140)(e), except that that would create a closure function every execution.


Since the non-generator/non-closured alternative to this is basically a function with a for loop, it's short through closures, though only if you actually use the paid_more_than thing regularly.

Of course, the above example is a little silly by being one-shot, as the for loop:

amount=140
for e in employees:
    if e['salary']>amount:
        print e

...is good enough.

Possible caveats

Read-only scope issues

Nested closures

Easier memory leaks