Monday, December 28, 2015

Undoable Undo Commands and the Singularity


Anders Holmgren suggested a nifty implementation for the command pattern in Dart. Part of his solution has me flummoxed.

I do not think the problem is with Anders' solution. Having spent two days exploring it, I think it is solid. After last night, I do not believe the problem is with Dart either. I think the problem is mostly between my ears. That or I am experiencing the second of the two hardest things in programming:
  1. cache invalidation
  2. naming things
  3. off-by-one errors
The naming problem that I may be experiencing is that I have been working with objects of type Command. Anders solution used functions named Command while his classes were named CommandClass. So whenever his solution referenced Command, it meant the function, but I kept thinking the object.

This brings me to a question. When working with functions and function-like objects in Dart, how should I name them? Normally, I do not care for Hungarian notation. But I cannot think of anything better than calling one Command while the other gets the Hungarian treatment (either CommandFunction or CommandClass). I am not sure either is a good approach. Having already explored the other option, tonight I will explore how things look with a Command class and a CommandFunction typedef.

I should stress that I am not attempting to argue with Anders' solution. He adapted a working solution to my existing code, which I hugely appreciate. The working solution neatly sidesteps this naming issue. I may adopt that solution, but I first would like to see how this plays out. I also note that I strongly risk confirmation bias here—CommandClass confused me when I first looked at it and I named my class my original class Command. That said...

I start with a CommandFunction typedef:
typedef void CommandFunction();
My commands will all have void return type and take no arguments. Thus, any functions with that signature are "Command Functions". For example, the functions supplied to the "Hi!" and "Scare" buttons are both a CommandFunction:
  var btnSayHi = new Button("Hi!", (){ robot.say("Hi!"); });
  var btnScare = new Button("Scare Robot", (){ robot.say("Ahhhhh!"); });
In Dart, any class that declares a call() method is also a function. So any class that implements the abstract Command class will be a CommandFunction:
abstract class Command {
  void call();
}
The MoveNorthCommand, for instance, should be a CommandFunction:
class MoveNorthCommand implements Command {
  Robot robot;
  MoveNorthCommand(this.robot);
  void call() { robot.move(Direction.NORTH); }
  void undo() { robot.move(Direction.SOUTH); }
}
And indeed, when I check an instance of this class, it does report that it is a CommandFunction:
  var moveNorth = new MoveNorthCommand(robot);
  print("Is moveNorth and CommandFunction? ${moveNorth is CommandFunction}.");
This results in:
Is moveNorth and CommandFunction? true.
There is not much that I can do with the CommandFunction in my current implementation. The Command class cannot implement a typedef. Classes in Dart, even Function classes, can only implement other classes, not typedefs. So this means that Command is implicitly a CommandFunction, but there is no way to make that relationship explicit:
typedef void CommandFunction();

abstract class Command implements Function {
  void call();
}
What I can do, as I have done with the CommandClass approach, is replace references to Function with the CommandFunction typedef. Thus, a command in the Button class must be a CommandFunction:
class Button {
  String name;
  CommandFunction command;
  Button(this.name, this.command);

  void press() {
    print("[pressed] $name");
    command.call();
    History.add(command);
  }
}
I can also constrain History so that it can only add CommandFunction objects:
class History {
  // ...
  static void add(CommandFunction c) {
   // ...
    _h._undoCommands.add(c);
  }
  // ...
}
While I am writing those, I have to admit that they do feel a little awkward. I want to talk about "command objects" or "command interface objects" instead of "command function objects." I can rationalize a little bit that these used to be "function objects" and the addition of "command" make the type clearer. But I honestly do not know if that stands up to the 6-months-later smell test. Regardless, I push on.

Currently, were I declare an UndoableCommand, it would implement the Command class (making it a CommandFunction) and add an undo() method:
abstract class Command implements Function {
  void call();
}

abstract class UndoableCommand implements Command {
  void undo();
}
I can replace Command as the implemented class in most of my commands with UndoableCommand without missing a beat. There are some other benefits as well, but where things get really interesting is with last night's typedef-as-a-generic-upper-bound solution.

This comes directly from Anders' solution and is pretty slick. Instead of declaring an undo() method in UndoableCommand, I make undo another command object:
abstract class UndoableCommand<C extends CommandFunction> implements Command {
  C get undoCommand;
}
As I found last night, I may not be able to extend a typedef in a class declaration, but I can do so in a generic. What this does is declare that the upper bound of the undoCommand has to be a CommandFunction. That is, I have explicitly constrained undoCommand to be a CommandFunction.

This allows me to rewrite my commands with commands that are themselves undoable commands:
class MoveNorthCommand implements UndoableCommand {
  Robot robot;
  MoveNorthCommand(this.robot);
  void call() { robot.move(Direction.NORTH); }
  UndoableCommand get undoCommand => new MoveSouthCommand(robot);
}
Just like replacing callbacks with objects in the original implementation the command pattern, this gives me more control and options with undo commands. Plus I can explicitly constrain these beasties to be CommandFunction thanks to the extended typedef. That remains small comfort since I cannot explicitly constrain the original Command class to be a CommandFunction. Still it's something.

As for the what gets the Command name question, I think I am going to give into confirmation bias here. I admit that CommandFunction got awkward in the History class, but I think that awkwardness was better than naming the typedef as Command and then mistaking it 6 months later for a command pattern object instead of a typedef. In the end, it is likely advisable to come up with different names altogether. Anders calls the typedef Callable in the working code, which is just another name for Function which might make it a bit too generic. Still, the resulting code reads nicely.

In the end, I think I finally have a handle on this. Big thanks again to Anders for the suggestion—undoable undo actions was a very worthwhile exploration. The typedef thing I could have done without (brain hurts!), but I should know it better... and now I do!

Play with the code in DartPad: https://dartpad.dartlang.org/61b96c6edfb1074c77f4.


Day #47

4 comments:

  1. You should be careful here with Circular Dependencies https://en.wikipedia.org/wiki/Circular_dependency that you're introducing with undo command getter on move north referencing move south and move south referencing move north. The wiki link I gave gives a solution, which could lead to your next pattern :-)

    ReplyDelete
    Replies
    1. If I explored all of the problems that I introduced in my code, I'd have 1000+ blog posts ;P

      But seriously, thanks for the link, I'll definitely give that topic a go in the near future!

      Delete
  2. In order to alleviate your typedef brain pain (or possibly make it worse ;-)) I thought I'd elaborate a little.

    The purpose of the command pattern is to reify arbitrary method invocations. It's origins are in languages like C++ that didn't have functions as first class objects.
    In languages like Dart that do have first class functions, you get the command pattern for free with closures.

    For example

    String a, b, c, d;

    var command1 = () => someObject.doSomething(a, b, c, d);
    var command2 = () => someOtheObject.doSomethingElse(1, 2, 3);

    both command1 and command2 have essentially the same interface so we can invoke them consistently. e.g.

    var commands = [command1, command2];

    commands.map((c) => c());

    To improve typing for these commands we can add a simply typedef for them

    typedef Command();

    processCommands(Iterable < Command > commands) {
    commands.map((c) => c());
    }

    So you have all the core stuff you need for command patterns in Dart out of the box.

    The limitation of using functions for commands comes in when you want to add more properties / methods to the commands.
    For example adding undo support requires capturing how to undo a command together with the command.

    Dart's cool function emulation support means that we can get a class to adhere to a typedef by following a simple convention

    e.g.

    class Command3 {
    var yetAnotherObject;

    call() => yetAnotherObject.doYetAnotherThing('foo', 66);
    }

    implements the Command typedef

    So I can do

    var command3 = new Command3()..yetAnotherObject = o;

    processCommands([command1, command2, command3]);


    I agree it is a great pity that we can't express that relationship in some way. I would love to see that added to the language.

    So in that absence it may be useful to add a base class for class based commands. e.g.

    abstract class CommandClass {
    call();
    }

    then

    class Command3 implements CommandClass { .. }

    But as you alluded to this is only because I can't express directly that Command3 implements the typedef Command in some way.

    So this is a long winded explanation of why I named them that way. Although in reality I didn't put that much thought into it ;-)

    Mind you I often don't use the word Command in my naming for command objects anyway. There are so many different variations in languages like Java.
    e.g. Runnable, Callable, Action etc which are all different variants of the command pattern.

    Either way it is less of a burden to pass in closures than needing to create a subclass, so I think it is very valuable to make the typedef the core abstraction for commands and only bring classes in when it is needed.

    ReplyDelete
    Replies
    1. Much thanks for that explanation -- it really does help to clarify my thinking on the topic. The first time that I saw your code, I really didn't _get_ it (obviously). But having worked through it and then reading this explanation really helped.

      Seriously, much thanks for ALL of the guidance. I owe you a beverage of your choice at a future conference-type event!

      Delete