3 ms·
For 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 o
by malkia 15y ago
For 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....