Thursday, January 19, 2012

Mapping the Future

So yesterday I looked at Lists. Today, I'm onto Maps. As has been the way, this is based off of the information I read from Seth Ladd's Blog series on Dart. Maps and Hashes in Dart.

Maps are a great utility in web programming especially. They are an efficient way to create a relationship between a 'key' to a value. In Dart values can be of any type, and similar to Lists, they do not have to be of the same type. That is a map can contain keys which are strings, numbers, and other classes including Lists etc.

Unlike some other languages though (Ruby for instance), the 'key' in a map must be a string. It cannot be an instance of a class for example. You can't use a number as a key (which would cause confusion with Lists). 99.99998% of the time this won't be a problem as there is only a very rare case you would need anything other than a string. I will admit though I did enjoy the lighter-weight use of 'symbols' in Ruby, particularly for maps. But different roles for the languages. [edit:] I see a note at the bottom of Seth Ladd's blog indicating that this may change in the future. [/edit]

In Dart, as in most (but not all other languages), when you query a map for a key that doesn't exist, null is returned. I much prefer this over the alternative of a runtime 'indexOutOfRange' error as I have seen in another language. So for instance if we have the following:
main() {
  var map = { "Hello" : "World" };
  print(map['Foo']);
}

The result is:
null

We can then do a simple check to see if the value is null and assign it if we have to. Its also extremely easy to add to a map. Even if the key doesn't exist, if we assign to it, it will be added. So for instance we can do the following:
main() {
  var map = { "Hello" : "World" };
  map["Hello"] = "Joe";
  map["foo"] = "bar";
  // Map now contains { "Hello" : "Joe", "foo" : "bar" }
}

So that's nothing new to people familiar with Maps from other languages and with those two methods we have an easy way to check for a value and assign one to it if it doesn't exist otherwise. However there is an interesting function that exists in Dart for Maps: putIfAbsent.
This function allows us to query a map for a key and returns its value. However if the key was not previously defined, it will call the function you pass to it and assign the return value to that key. Lets look at an example:
main() {
  var map = { "Hello" : "world" };
  var res = map.putIfAbsent('foo', assign() {
      // Do some stuff
      return 'bar';
    });
  print(res);
  print(map['foo']);
}
bar
bar

So as you can see map is queried for the key 'foo'. When it does not find it, it then calls the function (in this case I named it assign). You can write some code to calculate a value and then whatever is returned is the value that is assigned to that key in map. That value is then also returned by map.putIfAbsent and assigned to the res variable. Something important to note however: putIfAbsent checks if the key is absent, not the value of that key. That means if the key has already been assigned but is simply null, the method will return null and not call the function that you pass to it. As the following demonstrates:
main() {
  var map = { "Hello" : "world", "foo" : null };
  var res = map.putIfAbsent('foo', () => 'Bar');
  print(res);
  print(map['foo']);
}
null
null

This is the same as the containsKey method. The method verifies if the key exists in the map already, not if it has a non-null value.

Another interesting tidbit is if you want to run in checked mode, or at least generate warnings when working with maps, you have option to specify the types to be expected. As previously mentioned, at the moment the key must be a string, but the capability, using Generics, allows you to catch potential errors or surprises. Seth does a great job explaining the use of generics for Maps on his blog, so check it out there.

No comments:

Post a Comment