Closure: what is a good use case? Why not a functor? And are there any negative ones?

I recently dived into Python. I used to program mostly numerical code and data analysis code in C ++ and Matlab. I saw a lot of discussion about Python and Ruby, and about closure. Almost all examples looked like this:

>>> def makeAdder(y): ... def myAdder(x): ... return x + y ... return myAdder ... >>> f = makeAdder(10) >>> f(5) 15 

I understand that this can be useful in a way. However, realistically, behavior in such situations (as if read-only) can be easily emulated by an object (functor):

 >>> class MyAdder(object): ... def __init__(self,y): ... self.y = y ... def __call__(self,x): ... return self.y + x ... >>> f = MyAdder(5) >>> f(10) 15 

An object does not take up much more space for code, and it is much more universal. It is also much easier to track and debug subsequent code.

In this case, we only read the non-local variable. But we can also write him: in Ruby, naturally, in Python using a non-local keyword. The object also supports this. But with the object, you have data gathered together so that you know exactly what is happening. Closing can potentially transfer variables in a completely opaque way, and this can lead to code that is surprisingly difficult to debug. Here is a really strange example:

 irb(main):001:0> def new_counter irb(main):002:1> x = 0 irb(main):003:1> lambda { x +=1 } irb(main):004:1> end => nil irb(main):005:0> counter_a = new_counter => #<Proc: 0x00007f85c6421cd0@ (irb):3> irb(main):006:0> counter_a.call => 1 irb(main):007:0> counter_a.call => 2 

At least for me this behavior is not intuitive. It can also cause a memory leak. This gives you a huge amount of rope to hang yourself. Again, this is especially true in Ruby, where you do not need to include this explicitly (unlike Python), and because in Ruby there are blocks throughout their main code that have access to everything. If an external variable changes because it is in closure, if you go through this closure, you can change the variable indefinitely and out of scope from where it lives. Contrast to an object that always carries its data safely.

Why do you hear a lot of talk about how good closures are, and how they should potentially be included in Java, how they sucked when they were not completely in Python, etc.? Why not use a functor? Or reorganize code to avoid, given how incredibly dangerous they are? To clarify, I am not one of those who foams in childbirth OO. Did I underestimate their use, overestimate my danger, or both?

Edit: maybe I have to distinguish between three things: closures that are read only once (this is what my example shows and almost everyone discusses), closures that are read in general, and closures that write. If you define a function inside another function using the local variable for an external function, there is almost no chance that it will return to haunt you. The variable in this space is inaccessible in any way that I can think of, so you cannot change it. This is a fairly safe and convenient (perhaps more than functor) way of generating functions. On the other hand, if you create a closure inside a class method or inside the main thread, it will read the variables every time you call, which can be accessed from other places. So that can change. I think this is dangerous because a private variable does not appear in the function header. You could say a long close on page 1 of your code, which closes with the main thread variable x, and then modifies x for unrelated reasons. Then reuse the closure and get weird behavior that you don't understand, which can be difficult to debug. If you really write nested variables, then, as my Ruby example shows, you really have the potential to create mess and cause unexpected behavior.

Edit2: I gave an example of strange behavior from closures for third use by writing non-local variables. Here is an example of strange (not so bad) behavior from second use (defining closures in areas where their private variables can be changed):

 >>> fs = [(lambda n: i + n) for i in range(10)] >>> fs[4](5) 14 
+7
source share
1 answer

readability. Your Python example shows how the more obvious and easier to read version of closure compares with a functor.

We also carefully avoid creating a class that does nothing, but acts as a function - this is the smell of redundancy.

If nothing else, when we do something, it makes sense to describe it as an action, not an object .

As another note, an example of where these structures are used a lot in Python is of great importance, is a decorator. Most function decorators do something similar, and they are a really useful feature.

Edit: as a status note, remember that functions are not special in Python, they are still objects:

 >>> def makeAdder(y): ... def myAdder(x): ... return x + myAdder.y ... myAdder.y = y ... return myAdder ... >>> f = makeAdder(10) >>> f(5) 15 >>> fy 10 
+10
source

All Articles