Monday, November 19, 2012

Making a Dirty HashMap in Dart

‹prev | My Chain | next›

While working through my dart-dirty local data store yesterday, I realized that I was making a key-value store in Dart without making use of Dart's key-value class, the HashMap. There are a number of abstract methods in HashMap that need implementing a base classes. Let's see how many I can get done tonight.

First up, I declare my Dirty class as implementing HashMap:
#library('dart_dirty');

#import('dart:io');
#import('dart:json');

class Dirty implements HashMap<String, Object> {
  // ...
}
I do not necessarily need to restrict myself to String keys in a HashMap—anything that is hashable would do—but since I am serializing them as JSON, I do need the string.

After declaring that my class implements the abstract HashMap, I expect that my tests would fail if run in type-checked mode. Instead, they all continue to pass:
➜  dart-dirty git:(master) ✗ dart --checked test/dirty_test.dart
unittest-suite-wait-for-done
PASS: new DBs creates a new DB
PASS: writing can write a record to the DB
PASS: reading can read a record from the DB
PASS: reading can read a record from the DB stored on the filesystem

All 4 tests passed.
I suppose this makes sense—or at least I can rationalize this behavior. Type checking is simply ensuring that the types being passed conform to what is declared. If I want a whole lot of complaining about not implementing the abstract class properly, I need to turn to the dart_analyzer tool:
➜  dart-dirty git:(master) ✗ dart_analyzer lib/dirty.dart 
file:/home/chris/repos/dart-dirty/lib/dirty.dart:6: Concrete class Dirty has unimplemented member(s) 
    # From Map:
        Collection<String> keys
        Collection<Object> values
        int length
        bool isEmpty
        bool containsValue(Object)
        bool containsKey(String)
        Object [](String)
        void []=(String, Object)
        Object putIfAbsent(String, () -> Object)
        Object remove(String)
        void clear()
        void forEach((String, Object) -> void)
     5: 
     6: class Dirty implements HashMap<String, Object> {
              ~~~~~
Ahhhh, that's the stuff.

The two that I am most keen to define are the setter and getter operator. So far, I have been following the node-dirty convention of making set() and get() methods. That's just wrong in Dart.

So I update my test for retrieving records to use the new syntax:
    test("can read a record from the DB", () {
      var db = new Dirty('test/test.db');
      db.set('everything', {'answer': 42});
      expect(
        db['everything'],
        equals({'answer': 42})
      );
    });
I can make that test pass by replacing the get method:
class Dirty {
  // ...
  dynamic get(String key) => _docs[key];
  // ...
}
With the appropriate operator definition:
class Dirty {
  // ...
  Object operator[](String key) => _docs[key];
  // ...
}
Similarly, instead of setting values in the DB with a set() method:
db.set('everything', {'answer': 42});
I now want to use the Darty []= operator:
db['everything'] =  {'answer': 42};
To make that happen, I remove the set() method:
class Dirty {
  // ...
  void set(String key, value) {
    _docs[key] = value;
    // ...
  }
  // ...
}
And replace it with a []= operator:
class Dirty {
  // ...
  void operator []=(String key, Object value) {
    _keys.add(key);
    // ...
  }
  // ...
}
After updating my tests to use the new syntax, I again have all four tests passing:
➜  dart-dirty git:(master) ✗ dart --checked test/dirty_test.dart
unittest-suite-wait-for-done
PASS: new DBs creates a new DB
PASS: writing can write a record to the DB
PASS: reading can read a record from the DB
PASS: reading can read a record from the DB stored on the filesystem

All 4 tests passed.
As for the bulk of the remaining not-implemented abstract methods of HashMap, I can define most by delegating to the method of the same name on the internal _docs variable:
class Dirty {
  // ...
  int length => _docs.length;
  bool isEmpty => _docs.isEmpty;
  Collection<String> keys => _keys;
  Collection<Object> values => _docs.values;
  bool containsValue(Object v) => _docs.containsValue(v);
  bool containsKey(String k) => _docs.containsKey(k);
  // Object putIfAbsent(String, () -> Object)
  // Object remove(String)
  // void clear()
  void forEach(cb) => _docs.forEach(cb);
  // ...
}
Unfortunately, I seem to be running an older version of the Dart SDK because I now get syntax errors when I run my tests:
➜  dart-dirty git:(master) ✗ dart --checked test/dirty_test.dart
'package:dart_dirty/dirty.dart': Error: line 33 pos 14: unexpected token '=>'
  int length => _docs.length;
             ^
'file:///home/chris/repos/dart-dirty/test/dirty_test.dart': Error: line 2 pos 1: library handler failed
#import('package:dart_dirty/dirty.dart');
^
If I add empty parentheses to the length method definition, then my tests again compile and pass, but this is not correct Dart behavior. In the most recent Dart, the empty parentheses on Dart properties should be omitted.

Satisfied with my progress, I call it a night here. I will install the latest SDK and get started on the destructive HashMap methods tomorrow.


Day #574

No comments:

Post a Comment