2013年5月8日水曜日

Future を使いこなす(1)

このエントリーをはてなブックマークに追加
Dart の Future についてはだいぶ前に解説(Dartで未来(Future)の処理を実装する)を書きましたが、それから使い方が少し変わっていますので、現時点での最新仕様(Milestone 4)をベースに改めて説明を書きました。

目次
  • Future を使いこなす(1)
    • Future とは何か(おさらい)
    • 基本の Future の書き方
    • エラー発生時の処理を記述する
  • Future を使いこなす(2)
    • Future を続けて処理する
    • 連結時のエラー処理を実装する
    • リストの要素を Future で順に処理する
    • 複数の Future をまとめて待つ
  • Future を使いこなす(3)
    • Future を返す関数を実装する
    • Future をテストする

Future とは何か(おさらい)

Future は Dart のライブラリとして提供されている、非同期処理のための機能です。結果がすぐに得られるかわからない処理を同期的に書いてしまうと、その処理が終わるまで次の処理がブロックされてしまいます。例えば Web からリソースを取得するとき、いつ結果が返ってくるか(あるいはエラーになるのか)を予測してプログラミングすることはほとんど不可能です。

それでは都合が悪いので、どのくらい時間がかかるかわからないような処理は非同期で実行し、結果が出た時にそれに対応する処理を実行させるということをよくやります。これを実現するための機能が Future です。

基本の Future の書き方

Future の機能は dart:async ライブラリで提供されていますので、プログラムの先頭で import する必要があります。
import ('dart:async');

void main() { /* ... */ }
同期的な関数は戻り値として処理結果を返します。エラーが発生した場合には例外を送出します。
// 同期的な関数
String concat(String a, String b) {
  return a + b;
}

void main() {
  // Hello world
  print(concat('Hello ', 'world'));
}
concat 関数から戻った時点で print 関数へ渡す結果が出ています。これに対して非同期的な関数では、関数から戻った時点では結果が出ているかどうか分かりません。そのような場合、Dart の関数は Future のインスタンスが返るようになっています。処理が終わると Future#then で指定した関数がコールバックされますので、そこに結果の処理を記述します。
import ('dart:async');

// 非同期的な関数
Future<String> asyncTask() { /* ... */ }

void main() {
  // Future を受け取る。
  var f = asyncTask();

  // Future#then に完了時の処理を書く。
  f.then((String s) => print(s));
}
実践的なプログラミングでは次のように短く書かれます。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask().then(print);
}

エラー発生時の処理を記述する

非同期処理の中でエラーが発生した時や、then で登録したコールバック関数が例外を送出した時には、then の onError 引数に指定した関数が呼ばれます。
import ('dart:async');

Future<String> asyncTask() {
  // 非同期処理中にエラーが発生したり...
}

void main() {
  asyncTask().then(
    (s) {
      // コールバック関数内で例外が発生すると...
    },
    // onError が呼び出される。
    onError: (e) => print('ERROR: $e'));
}
あるいは Future#catchError を使って次のように書くこともできます。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask()
    .then(print)
    .catchError((e) => print('ERROR: $e'));
}
エラーの処理では、エラーの型や内容によって処理を分けることがよくあります。Future#catchError では test 引数に bool 型の戻り値を持つ関数を渡し、そのコールバック関数でエラーを処理するかどうかを決められます。test 引数で指定した関数が true を返すとコールバック関数が呼び出され、false を返すと呼び出されません。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask()
    .then(print)
    // int の場合はこちら
    .catchError((e) => print('ERROR No.$e'),
                test: (e) => e is int)
    // String の場合はこちら
    .catchError((e) => print('ERROR Message: $e'),
                test: (e) => e is String);
}
then と catchError を使った書き方は、同期処理における try-catch 構文に相当します。非同期処理と then で登録する関数は try ブロック、catchError で登録する関数は catch ブロックの処理に対応しています。そして finally に相当する Future#whenComplete も用意されています。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask()
    .then(print)
    .catchError((e) => print('ERROR No.$e'),
                test: (e) => e is int)
    .catchError((e) => print('ERROR Message: $e'),
                test: (e) => e is String)
    .whenComplete(() => print('Completed.'));
}
正常に終了した場合は then と whenComplete、エラーが発生した場合は catchError と whenComplete で登録した関数が順に呼ばれます。

Future を使いこなす(2) へ続く

2013年4月3日水曜日

カスケード呼び出しを使う

このエントリーをはてなブックマークに追加
ひとつのオブジェクトに対して複数のメソッドを呼び出す処理を書くことは多いと思います。このような操作をカスケード呼び出しと言いますが、Dart ではこの呼び出し方を専用の書き方を使って簡潔に書くことができます。

ここに動物のリストがあったとします。
<ul>
  <li id="bull">Bull<li>
  <li id="cat">Cat<li>
  <li id="dog">Dog<li>
</ul>
人気投票をしたところ Cat が 1 位になったので、太字にして王冠のイラストを追加してみましょう。
var licat = document.query('#cat');
licat.append(new Element.html('<img src="crown.png">'))
licat.style.fontWeight = 'bold';
カスケード呼び出し(..)を使うと次のように書くことができます。
document.query('#cat')
    ..append(new Element.html('<img src="crown.png">'))
    ..style.fontWeight = 'bold';
先のコードに比べて読みやすい形で表現されていると思います。呼び出すメソッドの数が増えるほどカスケード呼び出しを使うメリットが増えます。

ここで document.query('#cat') の戻り値は li 要素になります。子要素を追加する append メソッドを普通に呼び出すと、その戻り値は追加した要素、つまりここでは img 要素です。しかし .. を使って呼び出すと、元々のメソッドの戻り値は無視され、元のオブジェクトが返ってきます。
var img = new Element.html('<img src="crown.png">');

// e は追加した子要素(つまり img 要素)
var e = document.query('#cat').append(img);

// f は document.query('#cat') の値(li 要素)
var f = document.query('#cat')..append(img);
カスケード オペレーターでは結果が元のオブジェクトになるので、同じオブジェクトに対して次のメソッドを呼び出すことができるわけです。

.. は [] にも使うことができます。
document.query('#cat').attributes
    ..['style'] = 'color: red'
    ..['title'] = 'popular';

2012年8月17日金曜日

マイルストーン1を追いかける

このエントリーをはてなブックマークに追加
GoogleのDart開発者たちは、マイルストーン1という区切りへ向けて開発を進めています。Dart EditorやEclipse用Dartプラグインのような開発環境や、Dart VMやdart2jsのような言語環境は日々更新されており、そしてDart言語仕様についても変更が予定されています。

言語仕様についてはBugs and Feature Requestsメーリングリストで議論されており、マイルストーン1に向けての大きなトピックがMilestone 1 Language Changesという文書でまとめて公開されています。

ただしマイルストーン1の内容がすべて固まっているわけではなく、2012年8月17日現在で公開されている言語仕様は0.10ですが、マイルストーン1の内容は含んでいません。近いうちに0.11が公開されることになっていて、その内容はほぼマイルストーン1の仕様を反映したものになるようです。

特にcode.google.comに公開されているBugs and Feature RequestsのページではDartのバグや要求がIssuesとして公開されており、これを見るとプロジェクトの進み具合がなんとなく分かります。マイルストーン1に向けたIssuesはhttp://code.google.com/p/dart/issues/list?can=2&q=milestone=M1というURLで確認できます。

また、この内容をChromeのボタンで監視できるサードパーティー製のエクステンションがDart Milestone 1 monitorという名前で公開されており、これをインストールするとChromeでIssuesの数を見ることができます。まだ300個前後を行き来していますので収束するにはしばらくかかりそうですが、なんとなく見ているのも面白いと思います。

英語の得意な方は、Dart開発者のビデオキャストやポッドキャストDartisansをチェックするのもよいでしょう。