🍖

【Dart】call() メソッドを効果的に使う

2021/08/23に公開

ちょっとした豆知識ですが、 Dart の関数型のオブジェクトには call() という特別なメソッドが用意されています。

これは通常の関数の呼び出しと同じ挙動をするもので、例えば以下の requestArticles() 関数は以下の2通りの書き方で呼び出すことが可能です。

List<Article> requestArticles() {
  // ... 何か記事一覧を取得する処理
}

// 以下の2つは同じ結果になる
requestArticles();
requestArticles.call();

通常は .call がつく分タイプ数が多くなってしまうため積極的にこの書き方をすることはありませんが、呼び出したい関数オブジェクトが nullable な場合 のみ、これを使うことでコードを簡潔に書くことができます。

例えば、 Flutter では自作の Widget が onTap などのコールバックを受け取ることがあると思いますが、それが nullable な場合、以下のように書くことが可能です。

class SomeClickableWidget extends StatelesWidget {
  final VoidCallback? onTap; // コールバック。 null の場合は何もしない。
  
  ...省略
  
  TextButton(
    onPressed: () => onTap?.call(), // ?.call() で1行で書ける。
    // onPressed: () {
    //   if (onTap != null) {  // わざわざnullチェックして呼び出す必要はない
    //     onTap!();
    //   }
    // }
  ),

  ...省略
}

いちいち if で null チェックして ! で強制的に non-null に変換して、ということをしなくても、 ?.call() で呼び出すことで onTap が null の場合は何もせず、 null でなければ関数を呼び出す という仕組みが1行で書けるようになります。

ちょっとした違いですが、意識して使うことでコードを少しスッキリさせることができるでしょう。

余談

ちなみに、 関数型はのオブジェクトには call メソッドが用意されている という表現はもしかしたら因果関係が逆かもしれません。

以下の "A tour of the Dart language" によると、

https://dart.dev/guides/language/language-tour#callable-classes

To allow an instance of your Dart class to be called like a function, implement the call() method.

と書かれている通り、 call() メソッドが定義されたクラスのインスタンスは、関数のように () でその call() メソッドを呼び出すことが可能 です。

自作のクラスで call() を定義して () で呼び出し可能な何かを作ることはそれこそないかもしれませんが、知っておくと Flutter フレームワークやパッケージのコードリーディングに役立つかもしれません。

例えば riverpod パッケージの中でも AutoDisposeChangeNotifierProviderBuilder クラスがこの仕組みを使っていたりしました。

https://github.com/rrousselGit/river_pod/blob/0502b9a122454d450d574cbf7e95427b2a0ca94a/packages/flutter_riverpod/lib/src/builders.dart#L321

普通のクラスのインスタンスのはずなのに、なぜか関数みたいに () がついているのを見かけたら、この仕組みを思い出すと何をしているのかが見えてくるかもしれません。

Discussion