5 ms·
Closures – A Simple Explanation (Using Ruby)
- malkia 15y agoFor me one of the coolest ways of using closures was to implement undo system. It's not very efficient, but easy to write and compose. Basically every editor operation can return a function that does the opposite. For example (some lua-like language) function DeleteLine(lines, line_number) local deletedLine = lines[line_number] -- do the actual deletion - move elements up return function() InsertLine(lines, line_number, deletedLine) end end function InsertLine(lines, line_number, deletedLine) -- do the actual insertion return function() DeleteLine(lines, line_number) end end Again, not very efficient - but easy to grasp. Without closures would be hard to compose - you would have to take care of a lot of extra parameters where they need to be stored, etc.
- schrototo 15y agoThat's pretty neat, but returning the undo operation puts the responsibility of actually keeping track of undo operations on the caller side. I think it'd be better having each function push it's corresponding undo operation onto some global undo stack, thereby ensuring every operation gets recorded, without the caller having to do any work (or even know about the undo functionality). But of course, you don't really need closures for that. For example, Cocoa does it this way: - (void)deleteLineAtRow:(NSUInteger)row { NSString *deletedLine = [self lineAtRow:row]; [[self.undoManager prepareWithInvocationTarget:self] insertLine:deletedLine atRow:row]; // do actual deletion } - (void)insertLine:(NSString *)line atRow:(NSUInteger)row { [[self.undoManager prepareWithInvocationTarget:self] deleteLineAtRow:row]; // do actual insertion } That -prepareWithInvocation: stuff tells the undoManager to store the next message sent to it, along with it's parameters, on an internal stack. This is of course depends on Objective-C's dynamic nature and there's a whole lot of complexity hidden inside the undo manager object. But mixing this undo manager concept with closures should be pretty straightforward, resulting in something like this (note that this is a hypothetical example; Cocoa does not currently implement this functionality): - (void)deleteLineAtRow:(NSUInteger)row { NSString *deletedLine = [self lineAtRow:row]; [self.undoManager registerUndoWithBlock:^{ [self insertLine:deletedLine atRow:row]; }]; // do actual deletion } - (void)insertLine:(NSString *)line atRow:(NSUInteger)row { [self.undoManager registerUndoWithBlock:^{ [self deleteLineAtRow:row]; }]; // do actual insertion }
- malkia 15y agoThanks for that! I was mainly thinking that by returning functions, one can compose for more complex editor operations, more functions at once, and even simplify them (if they cancel each other). Off course this is not currently possible, unless the returned functions are some kind of AST tree, and there is something that can optimize them at runtime (dynamic programming?)... Probably overkill.... For example deleteBlock might delete 10 lines, and if there is better composability, instead of returning undo to insert 10 individual lines, it can be merged as one operation with 10 lines, basically by having rules, if you have production like "insertLine * insertLine * insertLine * insertLine" - you can make this as "insertLines[lines collected]" - but that's beyond the discussion. All this just to avoid making insertBlock/deleteBlock special, but reuse insertLine/deleteLine.... lost my thought already - too late in the morning....
- jabo 15y agoWow, I've seen functions like these in Erlang, but I didn't realize their usefulness until reading this.
- ttyS0 15y agoTo save you time, you can skip the initial rant about how closures are not taught in universities and start reading from the heading "A Concept Explained Simply".
- deleted 15y ago[deleted]
- colanderman 15y ago"the most popular languages that are in use right now don't support closures (Java, C++)" That is blatantly wrong. Closures, that is, the implementation structure which stores along with a code pointer the references or values to which local variables referenced by said code are bound, have been in Java since version 1.0. Otherwise you could not write code such as: class foo { final static int bar = 6; static class baz { int bla() { return bar; } } } and expect "(new foo.baz()).bla()" to return 6. What Java is missing (and I suspect what the author is conflating closures with) are anonymous functions, which require closures to function properly for the same reason inner classes require closures. Closures are not anonymous functions. Closures are an implementation detail which allow anonymous functions (among other structures) to act as expected in statically-scoped languages. Scheme has anonymous functions and closures. Java does not have anonymous functions but does have closures. Emacs Lisp has anonymous functions but does not have closures (and thus does not exhibit static scope). BASIC has neither anonymous functions nor closures. (It is worthwhile to note that there are other methods for implementing static scope such as alpha-renaming but they are usually much less efficient than closures.)
- spacemanaki 15y agoPlus, Java does have anonymous inner classes, which can be used in a fashion pretty similar to lambdas although with clumsier syntax than most people would like. Yours is a crucial point that I think got munged in the Java 7 closures debate, since a lot of Java people didn't know what closures were, and a lot of people on the outside maybe didn't know what Java lacked or didn't lack, so there were definitely a few blog posts that conflated them with lambdas.
- cconroy 15y agoGood examples of closures / first order functions in java is through a visitor; example below is like the ones in A Little Java, A Few Patterns new Top(new Anchovy(), new Top(new Tuna(), new Top(new Lettuce(), new Bot()))) .accept( new RemVisitor(new Anhovy() ); This removes those smelly Anchovies from your tuna sandwich -- and can be implemented with a closure or not. If accept() method returns a new Top w/o Anchovies and RemoveVisitor stores what to remove in its field then it is a closure. This book is cool for lisp programmers to learn programming with recursive objects like composites and decorators.
- rgrove 15y agoBack to the Future II is the best explanation of closures I've ever seen.
- colanderman 15y agoI consider myself well-versed on both closures and BttF II but I don't understand this comment. Care to explain?
- rgrove 15y agoI'm referring to the scene in which Doc draws a diagram on a blackboard to explain to Marty why the two of them still exist even though they've screwed up history and created an alternate 1985. Essentially, they exist because they're in a time closure and they carry their own pasts with them into 1985-B. It's not a perfect analogy, but it's close enough that I've been able to use that scene to help make closures click for someone who was having trouble understanding them.
- dmbass 15y agoThis explanation did not add anything to help me better understand closures and anonymous functions. I already understand that closures allow you to pass an anonymous function around and it maintains the variables that were in its scope when it was created (whether by copy or reference). The problem, at least for me, is finding a non-trivial example of using an anonymous function. Does anybody have other examples like malkia's[1] that demonstrate why anonymous functions and closures are useful? Or maybe just a good explanation of functional programming? 1. http://news.ycombinator.com/item?id=2919149 http://news.ycombinator.com/item?id=2919149
- viscanti 15y agoIn Scheme named functions are simply syntactic sugar for anonymous functions (or Lambda expressions). The same is true in Javascript. http://lpar.ath0.com/2010/10/14/javascript-when-is-a-function-not-a-function/ http://lpar.ath0.com/2010/10/14/javascript-when-is-a-functio...
- njharman 15y agoWhether a function is anonymous or not is sugar. The important thing is functions aren't special, they can be passed around (i.e. the are 1st class objects). I actually can't fathom having to code in a language in which you can't pass functions around. Call backs is my biggest use of function passing. This is really, really huge. Currying, decorating/wrapping functions Replacing methods/functions at run time. Great for testing. Putting them in data structures. Often dictionaries. Often to create more dynamic switch like logic. dict[key] = func_that_handles_key, etc. then later, dict[key](key) Lots of data is code stuff. Closures just make the passed functions more powerful / convenient as they capture their scope. Which is often used (at least by me in Python) to create function factories that return custom functions. A contrived example but a pattern I use often. Logging object with warn, debug, error functions. You could hardcode / boilerplate each of those functions but that is boring, tedious, and nonDRY. So instead you create a function factory and use it to return anonymous functions, like so. Python. def log_func_factory(level): def closure(msg): print level, msg return closure class logging(object): debug = log_func_factory('debug') warn = log_func_factory('warn') error = log_func_factory('error') With real code I'd not repeat those assignments and instead use a loop driven by data. for level in ('debug', 'warn', 'error'): setattr(logger, level) = log_func_factory(level) Bam, data driven code. Very DRY, very concise, very dynamic. Maintenance is adding one text string in one place.
- enneff 15y agoFrom the succinct description of closures: It remembers the values of all the variables that were in scope when the function was created. It is then able to access those variables when it is called even though they may no longer be in scope. No! It doesn't remember the values. It binds the variables. This is a major misstep in describing closures. The most important point: when the values of the bound variables change the closure sees the new values. Please, fix this! A better simple description of closures: * You can pass a closure around as a value (to be called later). * When you create a closure it is bound to the variables in its current scope. When you call a closure, it sees the current value of the bound variables (not their values at the time the closure was created). Follow this with a simple example, like: i := 0 inc := func() { i++ } println(i) // prints "0" inc() println(i) // prints "1" inc() println(i) // prints "2"
- shasta 15y agoDepends on the language. "Closure" (at least to me) doesn't imply this bevaior in a language with mutable variables. And then you have pure languages and languages which only support mutation through reference values, in which his explanation would be correct.
- Jach 15y agoIndeed. Python is a prime example of a language with closures that doesn't do variable-name binding, due to the way variables and specifically assignments work in general with Python.
- carbonica 15y agoI'm sorry? Python is the same way: >>> i = 10 >>> def x(): ... print i ... >>> x() 10 >>> i = 11 >>> x() 11 The value is not closed over in any language I can think of off the top of my head. It's a reference to a variable in lexical scope. What python does slightly differently is writing to variables in outer scopes. But it still closes over references.
- jrvarela56 15y agoI dont understand the difference between a closure and an object's state. Is an object one example of a closure? You can pass it around and it contains instance variables. What are lambdas for in this context? Simply calling the object's attribute would return the value set at creation - even if modified afterwards.
- jrvarela56 15y agoHope this helps: (Python) def closure(x): return lambda y:x+y a = closure(5) #'a' is a function now a(2) #returns 7 a(3) #returns 8 The function 'closure' can be carried around and it remembers the value of x from when it was created. Got the example from this article: http://linuxgazette.net/109/pramode.html http://linuxgazette.net/109/pramode.html (I believe it was shared a few weeks ago) - great reference for other FP concepts and a good way to get started in FP if you already like working with Python.
- lispm 15y agoClosures are functions plus their bindings for its free variables. The references to the bindings have lexical scope and indefinite extent.