Saturday, December 31, 2011

Anonymous Functions in Dart

‹prev | My Chain | next›

Now that I know how to execute Dart locally, I am ready to begin poking through the language a bit. First, I would like to explore the different ways to write anonymous functions. Be they used for currying, partial application, or simple callbacks, anonymous functions are a must in any language. Interestingly, I have already seen two different formats in Dart. I wonder how many more there are...

The one that I have been using for the past two days is a sort of naked argument list followed by the function body inside curly braces:
main() {
  var list = [0,1,2,3,4];
  list.forEach( (i) { print("Item: " + i); } );
}
(try.dartlang.org)

That format is very reminiscent of Ruby blocks:
list = [0,1,2,3,4]
list.each { |i| $stderr.puts "Item: " + i }
To be sure, there are more curly braces and semi-colons in the Dart equivalent, but it is nice being able to drop the function keyword from the Javascript equivalent.

If I save and run that Dart script, I get:
➜  command_line git:(master) ✗ dart iterator01.dart
Item: 0
Item: 1
Item: 2
Item: 3
Item: 4
Interestingly, it is possible to make this Dart code look even more Javascript-y with a "function" before the argument list:
main() {
  var list = [0,1,2,3,4];
  list.forEach( function(i) { print("Item: " + i); } );
}
(try.dartlang.org)

That "function" is not a keyword in dart however, it is the name of the function (in effect, I have named my function "function"). I could have called it "f":
main() {
  var list = [0,1,2,3,4];
  list.forEach( f(i) { print("Item: " + i); } );
}
(try.dartlang.org)

So what are those things? Named anonymous functions? Somehow that does not quite work. Named blocks? Maybe.

At any rate, they lend themselves to recursion nicely:
main() {
  var list = [0,1,2,3,4];

  list.forEach( f(i) {
    print("Item: " + i);
    if (i > 0) f(i-1);
  });
}
(try.dartlang.org)

Yah, it is pretty silly to do recursion in an iterator block, but still, it works:
➜  command_line git:(master) ✗ dart iterator04.dart
Item: 0
Item: 1
Item: 0
Item: 2
Item: 1
Item: 0
Item: 3
Item: 2
Item: 1
Item: 0
Item: 4
Item: 3
Item: 2
Item: 1
Item: 0
Just as in Javascript, it is possible to assign anonymous functions to variables:
  var f = (i) {
    print("Item: " + i);
  };

  var list = [0,1,2,3,4];
  list.forEach(f);
(try.dartlang.org)

And, yes, regular function naming works as well:
  f(i) {
    print("Item: " + i);
  };

  var list = [0,1,2,3,4];
  list.forEach(f);
(try.dartlang.org)

That pretty much covers this anonymous function format, but then there was also a hash rocket format that I encountered:
  var list = [0,1,2,3,4];
  list.forEach( (i) => print("Item: " + i) );
(try.dartlang.org)

This compact format seems to only work with a single expression. That is, both of the following are syntax errors:
  // wrong #1
  var list = [0,1,2,3,4];
  list.forEach( (i) =>
      print("Item: " + i);
      print("      " + i);
  );

  // #wrong #2
  var list = [0,1,2,3,4];
  list.forEach( (i) =>
      print("Item: " + i)
      print("      " + i)
  );
(try.dartlang.org)

The first fails on the semi-colon:
'/home/cstrom/repos/dart-site/examples/command_line/iterator10.dart': Error: line 4 pos 26: ')' expected
      print("Item: " + i);
                         ^
The second fails on the second print statement in what is supposed to be a single expression:
'/home/cstrom/repos/dart-site/examples/command_line/iterator10.dart': Error: line 5 pos 7: ')' expected
      print("      " + i)
      ^
But it does work if I combine those two statements into a function that can be called as a single expression:
  var list = [0,1,2,3,4];

  double_print(i) {
    print("Item: " + i);
    print("      " + i);
  }

  list.forEach( (i) => double_print(i) );
(try it on try.dartlang.org)

Checking through the language specification, it seems like those two formats (argument list + block and hash-rocket + expression) are the two available anonymous function formats allowed in Dart.

While looking through the language specification, I notice that there do appear to be optional arguments. While messing about, I had tried a Ruby-ish:
f(i, j=0) { /* ... */ ]
But that had not worked. Looking through the formal specification, I do not see any examples of optional arguments, but some of the discussion seems to show optional arguments inside square brackets:
main() {
  pretty_int(i, [label="Number: "]) {
    print(label + i);
  }

  pretty_int(1);
  pretty_int(42, "Answer to everything: ");
}
(try it on try.dartlang.org)

And that does seem to work as expected:
➜  command_line git:(master) ✗ dart optional_arg.dart 
Number: 1
Answer to everything: 42
With a pretty good handle of functions under my belt, I call it a night here. Up tomorrow, I think that I will play with currying functions in Dart. I love me some good currying.


Day #251

5 comments:

  1. Good article, keep them coming. Note though that Dart has string interpolation so you can do print("Item: $i") (or print("Item: ${i}")) instead of concatenating string using +.

    ReplyDelete
  2. Thanks for the interpolation hint. I prefer Ruby's hash ("Item: #{i}") to Dart's dollar sign, but maybe I'll get used to it :)

    Now if only Dart supported sprintf interpolation...

    ReplyDelete
  3. The => syntax is more or less equivalent of a lambda in Python. It evaluates a expression and returns the result. From the spec: "A function body of the form => e is equivalent to a body of the form {return e;}".


    It is mostly used for short method definitions:

    class Foo {
    int bar() => 1;
    int baz() => 2;
    }

    ReplyDelete
  4. Here are some other fun anon functions: http://try.dartlang.org/s/xpkl
    Only String gives a warning in non checked mode.

    ReplyDelete
  5. Also, for the simplest anonymous function, you can omit the function name:

    list.forEach( (i) { print("Item: " + i); } );

    Here: http://try.dartlang.org/s/K4Yl

    ReplyDelete