Saturday, May 26, 2012

Unlazily Static

‹prev | My Chain | next›

I ended up with an uglyish solution for mapping arrow keycodes to movement directions in some Dart code:
HashMap<int, String> _dir;
String keyDirection(int dir) {
  if (_dir) return _dir[dir];

  _dir = {};
  _dir[37] = 'left';
  _dir[38] = 'up';
  _dir[39] = 'right';
  _dir[40] = 'down';

  return _dir[dir];
}
The private variable _dir was bad form on my part—I was trying to avoid a perceived initialization penalty before I was actually affected by it. So I should have done something like:
String keyDirection(int dir) {
  var dir = {};
  dir[37] = 'left';
  dir[38] = 'up';
  dir[39] = 'right';
  dir[40] = 'down';

  return dir[dir];
}
In all honestly, even that is not terribly satisfactory. I would prefer to declare dir as:
var dir = {
  37: 'left',
  38: 'up',
  39: 'right',
  40: 'down'
};
Dart, however, does not allow object literal syntax to use integers as keys, so I am stuck with the individual assignments of the directions. And that continues to bother my sensibilities.

An astute reader suggest that I forgo the keyDirect() method entirely. Embracing the object-literal-with-string-key syntax, my key down handler can be written as:
  document.
    on.
    keyDown.
    add((event) {
      String direction = {
        '37': 'left',
        '38': 'up',
        '39': 'right',
        '40': 'down'
      }[event.keyCode.toString()];

      if (direction == null) return;

      event.preventDefault();
      me.move(direction);
      draw(me, context);
    });
That works and is better than what I had. Still, the actual lookup is obscured too much for my tastes. I would rather it be done via:
  document.
    on.
    keyDown.
    add((event) {
      String direction = _dirMap[event.keyCode.toString()];
      if (direction == null) return;

      event.preventDefault();
      me.move(direction);
      draw(me, context);
    });
I cannot simply declare _dirMap as a global variable:
// Fail: not a compile time constant
HashMap _dirMap = {
  '37': 'left',
  '38': 'up',
  '39': 'right',
  '40': 'down'
};

attachMover(me, context) {
  // ...
}
To make that work, I can make the left hand side of the _dirMap assignment a compile time constant with the simple addition of the const keyword:
HashMap const _dirMap = {
  '37': 'left',
  '38': 'up',
  '39': 'right',
  '40': 'down'
};

attachMover(me, context) {
  // ...
}
This brings me to another facet of the update Dart language spec: lazy evaluation of static variables.

In order to try this, I convert my entire game board into a Room class that includes a static variable version of _dirMap:
class Room {
  Player player;
  CanvasRenderingContext context;

  static HashMap _dirMap =  {
    '37': 'left',
    '38': 'up',
    '39': 'right',
    '40': 'down'
  };

  Room.play(this.player, this.context) {
    this.draw();
    this.attachMover();
  }

  draw() { /* ... */ }

  attachMover() {
    document.
      on.
      keyDown.
      add((event) {
        String direction = _dirMap[event.keyCode.toString()];
        if (direction == null) return;

        event.preventDefault();
        player.move(direction);
        draw();
      });
  }
}
My board is still working on load:


So maybe this works?

Uh, no. When I make my first move, I am greeted with the follow exception:
Internal error: 'file:///home/chris/repos/dart-book/book/includes/animation/main.dart': Error: line 37 pos 28: initializer must be a compile time constant
  static HashMap _dirMap = {
                           ^
So it seems that despite the spec, static variables are still unlazily evaluated. I add const to the Room class:
class Room {
  // ...
  static HashMap _dirMap = const {
    '37': 'left',
    '38': 'up',
    '39': 'right',
    '40': 'down'
  };
  // ...
}
And call it a night.

I am not too fussed by this. I would have been cool if this had made it into Dartium, but this means less work is needed to make a proper first release of Dart for Hipsters.


Day #398

No comments:

Post a Comment