2013年8月28日水曜日

Dart Editor をプロキシ経由で更新する

このエントリーをはてなブックマークに追加
Dart Editor はメニューの Tools - Preferences あるいは Help - About Dart Editor で更新できますが、プロキシ経由でインターネットにアクセスする環境では、更新サーバーに接続できずエラーになる場合があります。この場合は次の方法を試してみてください。
  1. Dart Editor を終了する。
  2. Dart Editor の実行ファイルと同じ場所にある DartEditor.ini ファイルをテキストエディタで開く。
  3. 「-Dhttp.proxyHost=」「-Dhttp.proxyPort=」という行を追加し、設定を書く。認証が必要なプロキシの場合は「-Dhttp.prxyUser=」「-Dhttp.proxyPassword=」も設定する。
  4. DartEditor.ini ファイルを保存する。

設定すると DartEditor.ini は次のようになります。
(前半省略)
-Xms40m
-Xmx1000m
-Dhttp.proxyHost=proxy.example.com
-Dhttp.proxyPort=8080
-Dhttp.proxyUser=account_name
-Dhttp.proxyPassword=account_password
Dart Editor 0.6.21_r26639 と、Windows XP および Ubuntu 13.04 の環境で確認しました。

2013年5月8日水曜日

Future を使いこなす(3)

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

Future を返す関数を実装する

非同期処理の関数を実装するときには Future のインスタンスを返すようにします。 関数の中で別の非同期処理関数を呼び出して適切な Future を返すということが多いと思いますが、場合によっては自分で非同期処理を書いて Future を返すこともあるでしょう。

自分で Future を生成するときには Completer を使います。
import ('dart:async');

Future<String> asyncTask() {
  var c = new Completer<String>();

  // ここで非同期処理を実行する。

  // Completer#future を返す。
  return c.future;
}

void main() {
  asyncTask()
    .then(print)
    .catchError((e) => print('ERROR: $e'));
}
非同期処理が完了したことを通知するためには Completer#complete を使います。 同じように、エラーが発生したことを通知するためには Completer#completeError を使います。

では試しに、Timer を使って非同期処理を作ってみます。
import ('dart:async');

Future<String> asyncTask() {
  var c = new Completer<String>();

  // 非同期処理
  const ONE_SEC = const Duration(seconds: 1);
  new Timer(ONE_SEC, () {
    c.complete('Pass');
    // エラーの時は
    // c.completeError('Fail');
  });

  // Completer#future を返す。
  return c.future;
}

void main() {
  asyncTask()
    .then(print)
    .catchError((e) => print('ERROR: $e'));
}
実行すると 1 秒後に then で登録した関数が呼びだされ、結果が表示されます。

Future をテストする

自動テストを実行するには unittest ライブラリを使います。 pubspec.yaml に unittest を追加してください。 Dart Editor ならば GUI で設定できます。 テキストエディターで変更する場合は、次のように dependencies に unittest を追加します。
dependencies:
  unittest: any
できたら pub install を実行します(Dart Editor の場合は自動で実行されます)。

まず同期処理の場合のテストコードです。
import 'dart:async';
import 'package:unittest/unittest.dart';

String syncTask() => 'Pass';

void main() {
  test('Test a function', () {
    expect(syncTask(), equals('Pass'));
  });
}
これに対して非同期処理のテストコードは次のようになります。
import 'dart:async';
import 'package:unittest/unittest.dart';

Future<String> asyncTask() {
  var c = new Completer<String>();

  const ONE_SEC = const Duration(seconds: 1);
  new Timer(ONE_SEC, () => c.complete('Pass'));

  return c.future;
}

void main() {
  // テスト
  test('Test a future', () {
    asyncTask().then(
      expectAsync1(
        (e) => expect(e, equals('Pass'))));
  });
}
Future#then や Future#catchError などに expectAsync1(1 は引数の個数)で取得した関数を渡してテストします。 同期処理のテストと若干が違いますが、おおよそ似たような形で書くことができます。

ただし expectAsync1 は今後の言語仕様の変更にともなって呼び出し方が変更される可能性があります。 あまり気にすることはないと思いますが、覚えておくと良いかもしれません。

Future を使いこなす(2)

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

Future を続けて処理する

ある Future の結果を元に、別の Future を使った処理を呼び出したいことがあります。その時は次のように書くことができます。
import ('dart:async');

Future<String> asyncTask1() { /* ... */ }
Future<String> asyncTask2(String s) { /* ... */ }

main() {
  Future<String> f1 = asyncTask1();
  Future<String> f2 = f1.then((s) {
    return asyncTask2(s);
  });
  f2.then((s) => print(s));
}
Future#then に指定したコールバック関数が Future のインスタンスを返す場合(つまりこのコードの return asyncTask2(s) のようになっている場合)、元々の Future(f1)と連結された新しい Future が then の戻り値(f2)となります。 したがってこのように書くと、asyncTask1 で生成された Future の処理が完了したあとに asyncTask2 で生成された Future が続けて処理されるようになります。

このコードは、より簡潔に次のように書くこともできます。
import ('dart:async');

Future<String> asyncTask1() { /* ... */ }
Future<String> asyncTask2(String s) { /* ... */ }

main() {
  asyncTask1()
    .then((s) => asyncTask2(s))
    .then((s) => print(s));

  // あるいは

  asyncTask1()
    .then(asyncTask2)
    .then(print);
}

連結時のエラー処理を実装する

Future を連結させた場合、どの Future で発生したエラーも最後の Future で受け取ることができます。 つまり次のように書けるということです。
import ('dart:async');

Future<String> asyncTask1() {
  // ここでエラーが発生しても...
}
Future<String> asyncTask2(String s) {
  // ここでエラーが発生しても...
}

main() {
  asyncTask1()
    .then(asyncTask2)
    .catchError((e) { /* ここでエラーが処理できる。 */ })
    .whenComplete(() { /* ここは最後に実行される。 */ });
}
見た目は少し異なりますが、try-catch と同じように扱えると考えれば迷わないでしょう。

リストの要素を Future で順に処理する

一つずつ Future を連結していくこともできますが、List のような Iterable な構造に入っているデータを順に Future で処理させるときには Future#forEach が使えます。
import ('dart:async');

Future asyncTask(String s) { /* ... */ }

main() {
  List<String> strs = ['a', 'b', 'c'];

  // strs の要素が一つずつ渡されて処理される。
  Future f = Future.forEach(strs, asyncTask);

  // 最後の要素が処理されると呼び出される。
  f.then((_) => print('Completed.'));
}
Future を then でつなげていった時と同じように、'a' の処理が終わったら 'b'、'b' の処理が終わったら 'c'、すべて終わったら最後の then という順番で処理されます。

途中の Future で値が返ってきてもそれらは捨てられてしまいますので、結果を受け取りたい場合には呼び出し元と呼び出し先で共通に参照できるオブジェクトを用意して、そこに結果を格納していくなどの方法を取る必要があります。

複数の Future をまとめて待つ

今度は Future をつなげて順に処理させるのではなく、複数の非同期で処理されている Future の完了をまとめて待つ方法です。 このような場合には Future#wait が使えます。
import ('dart:async');

Future asyncTask(String s) { /* ... */ }

main() {
  // List に複数の Future を入れておく。
  List<Future> fs = [
    asyncTask('a'),
    asyncTask('b'),
    asyncTask('c')
  ];

  // まとめて待つ。
  Future f = Future.wait(fs);
  f.then((_) => print('Completed.'));
}
List に入っている3つの Future はそれぞれ非同期に実行され、すべての Future が完了した時点で最後の Future の then に登録した関数が呼ばれます。 それぞれの結果はやはり捨てられますので、結果は別のオブジェクトに保存するなどしてください。

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

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';