🤔

【flutter】なんでforEachを使っちゃダメなの?【エラー深ぼり隊】

2024/11/04に公開

はじめに

ながすなりです。zennで投稿するのは初めてだったはずです。どうでもいいですね。本題に行きましょう。今日...というかさっき暇だったのでflutterでアプリ開発でもしてこんなコードをかいていました。

const week = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
week.forEach(
      (day) {
        tableCells.add(
          TableCell(
            child: Center(
              child: Text(
                day,
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
          ),
        );
      },
    );

あたくしはこんなコードをかきました。dart初心者なので気持ち悪かったりしたら許してください。ウコンの力を飲むと多少ましになると思います。

するとすぐにリンターちゃんがこうやっていってきました。

Function literals shouldn't be passed to 'forEach'.
Try using a 'for' loop.

皆さん英語はお得意でしょうからきっと翻訳なんていらずにすっと読んでしまうのでしょうが、僕は得意ではないので翻訳機にかけました。

関数リテラルを'forEach'に渡すべきではない。for'ループを使ってみてください。

...なるほどよくわからん。えせエンジニアの私には関数リテラルが何なのかよくわからん!文字列リテラルみたいなもんだろうなと思いつつも、ちゃんとは分からない。そっちは比較的どうでもよくてもう一つ浮かんでくる疑問が重要です。

なんでforEachに渡しちゃダメなの?

ここが気になったので深めていきましょう!記事を書きながら調べるか!という感じで、見切り発車なのでもしかしたら問題が浅い可能性もありますが。

じゃあまず関数リテラルって何なの?

私は常々思っているのですが、エンジニアが使用する専門用語ってその定義ってちゃんと検索しても出てこないのは何故なんでしょうね?医者の不養生に似たような何かを感じますね。関数リテラルを調べても、その定義が出てこないのは何か悲しいものを感じます。

と思ったのもつかの間、日本語で調べるのがあかんのかなと思って英語で調べたら出てきましたね。日本語でもその辺の定義の一覧が乗っているサイトとかあったら結構うれしいんだけどな...

https://www.oreilly.com/library/view/javascript-the-good/9780596517748/ch04s02.html

一部抜粋して、原義が変わらない程度に要約します。
記事曰く、「関数オブジェクトは関数リテラルによって作られる。」
記事曰く、「関数リテラルは四つに分かれている。まず「function」という文字列。そしてつけなくてもよいが関数の名前。次に引数のまとまり(ここ上手い日本語思いつかなかった)。最後に中かっこでくくられた一連の文。」

https://www.informit.com/articles/article.aspx?p=3089301&seqNum=3

またここには、「関数リテラルは特定の動きを持つ関数を指し示すである。」と書いてある。

つまりこれ!!!みたいな説明がなくちょっと抽象的なので、きっとふわっと理解するのがいいのだろうと思ってますが、上の定義や周辺情報を見るにまず形としては以下のような形なんだろうな。そしてそれはであると。分かったようなわからないような...まあでもふわっと理解できたので、また別の機会にきっと理解できるでしょう。

function (a, b) {
    return a + b;
};

なんでforEachに渡すとダメなん?

そもそも何で注意されてたかを整理しましょうか。

const week = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
week.forEach(
      (day) {
        tableCells.add(
          TableCell(
            child: Center(
              child: Text(
                day,
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
          ),
        );
      },
    );

https://dart.dev/tools/linter-rules/avoid_function_literals_in_foreach_calls
ここに書いてありますね。「関数リテラルを用いる場合はforEachを使うことは避けてください。」
forEachの中身に注目してみると、どうやら(day) {...}という形になっています。functionも名前もないですが、無名関数だと思うので、これがおそらく関数リテラルなのでしょう。そしてこれはforEachに使うべきではないと。

エラーの原因は理解できました。

forEachに関数リテラルを使うことは避けてください。関数リテラルとはこの場合だと(day) {...}の部分であると。だから、(day) {...}を使用しないでください。または使用する場合はforEachを使うのを避けたほうが良いと。そういうことなのでしょう。

これで理解した満足となったら、私は本当にえせエンジニアのままです。一体なぜ?forEachに関数リテラルを渡してはならないのか?ここをキチンと深堀りましょう。

どうしてforEachに関数リテラルを渡してはならないの?

https://dart.dev/tools/linter-rules/avoid_function_literals_in_foreach_calls
ここの続きにヒントになりそうなことが書いてあります。deepl翻訳済み。

forループを使うことで、開発者は自分の意図を明確かつ明示的に示すことができます。forループのボディ内のreturnは関数のボディから返されますが、forEachクロージャのボディ内のreturnはforEachのその反復の値のみを返します。forループのボディはawaitを含むことができますが、forEachのクロージャボディはできません。

この文章を解釈すると以下のことが読み取れそうです。

  1. forループのほうが開発者の意図を明確に示すことができる。読みやすいコードを書ける
  2. forループならreturnは関数のボディから返される
  3. forEachのreturnはその反復の値のみを返す
  4. forループのボディはawaitを含むことができる
  5. forEachのクロージャボディは出来ない

以上を考えると避けたほうがいいという表現は正しいように感じます。別に使ってもいいんだけどね?というような感じを受けます。

ただこれで終わりにするのも気持ちが悪い。まだなぜ意図を明確に示すことができるのか?が示せていません。

また2,3の意味もまだちょっとよくわかっていませんし、4,5もなぜそうなるのかまたクロージャってなんやねん!という疑問も湧いてきました。

ですが、これ全部やると非常に記事が長くなりそうなので、各自調べていただければと思います。本筋からは少々反れますし。

クロージャは奥が深そうだったので、きちんと理解しきれたとは言えないのですが、個人的には以下の記事が一番理解した気になれました。
https://analogic.jp/closure/

なぜ意図を明確に示すことができるのか?

結論から言うと恥ずかしいですがよくわかりませんでした。returnのことや、awaitのことこの辺はforループのほうが表現できる幅が広い(言葉を大分選びました)と、私は思いました。しかしながらそれがイコールで意図を明確に示すことができることにつながるのだろうかとは思っています。

例えば今回の場合であれば、returnもawaitもしません。ならば別にforループとforEachの間にそこまで差はないのでは?と思いました。だからリンターちゃんもwarningを出すだけで絶対に直せよ!とは言わないのかもしれません。

たしかにreturnやawaitを使う場合は、少々面倒なことになりそうなのは分かるのですが、それは関数リテラルを用いているときにforEachを使わないという話とイコールになるんですかね?少々理解が足りないような気がしますが、疑問のままですね。

またforEachを使用するのであれば、ほとんど無名関数を使う例のほうが多い気がしていて、ならforEachの存在意義っていったい何?という気持ちにもなります。名前付き関数を用いてやればいいんですかね?それともごく短いものであれば、簡潔に書けるからいいよ~ということなんでしょうか?おそらくは後者のような気がしていて、シンプルな例であるとき以外あまりforEachは使わない方がよいのかもしれません。

これ以上掘る方法もなさそうなので、この記事はこれまでですが、ほぼ未完成といっていい出来なので、物知りな読者諸君の補足コメントによってこの記事が完成されることを願います。他力本願で申し訳ないですが、未完成だから出さなくていいやよりも、未完成でもとりあえず出す。が私のモットーなので、それはどうなん?と思う方がいたら、どうぞ補足コメントでこの記事を一緒に完成させましょう!

書き直すとするならば?

for (var day in week) {
  tableCells.add(
    TableCell(
      child: Center(
        child: Text(
          day,
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
      ),
    ),
  );
}

たしかにこっちのほうが読みやすいような気がしなくもないですが、今回のケースにおいては微々たる違いのような気がします。リンターちゃんが怒るので、変えますが今回のケースでは変える必要はないのかもしれません。十分にシンプルなような気がしますし。

それではまたの機会にお会いしましょう。ながすなりでした。気分が良かったらxもフォローしてやってください。

Discussion