That'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
}
Thanks 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....
But of course, you don't really need closures for that. For example, Cocoa does it this way:
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):