Dartツアー(2)関数
Flutterを始めてみましたが、コードはDart言語で書きます。という訳で、Dart言語のツアーを見て勉強しながら、自分用にメモをまとめてみました。今回は関数です。
前回分は以下から。
関数
- 関数もオブジェクトである。変数に代入したり、関数の引数にできる。
- クラスのインスタンスを関数のように呼び出すことができる。
- Effective Dartでは公開APIでの型アノテーションを推奨しているが、なくても動作する。
- 関数が1行の場合は"=>(式)"を使うことができる。この場合は式を返す。
- 式しか書けないので、if文等の文は書けない。
引数
- 関数は任意の数の必須引数を持つことができる。
- それに続けて、名前付き引数やオプションの位置指定引数を指定できる。
- Flutterのwidgetコンストラクタのように、APIの中には必須引数でも名前付き引数しか使用しないことがある。
- 関数の引数でも末尾のカンマを許容している(コレクションの様に最後の引数の後にカンマをつけても構わない)
名前付き引数
- 名前付き引数は特に必要であると明示されていない限りはオプションである。
- 関数を呼び出すときは”引数名:値”の形で記載する。
- 関数を宣言する場合は"{ 引数1, 引数2, …}"という形で記載する。
- 引数をnullにできない場合、初期値を指定する。
- 必要な名前付き引数は引数の前に"require"を付ける。
サンプルですが、普通の引数と名前付き引数は混在できますし、明示してない名前付き引数はオプション扱いです。
オプション位置引数
- []で囲むとオプションの位置引数であることを示す。
こちらがサンプルです。名前付き引数と同じサンプルです。訳的にあまりすっきりとしないのですが、名前付き引数が名前を指定することで順番が関係ないのに対し、こちらは順番に依存する引数という意味になります。
名前付き引数と位置引数のどちらも使えることで柔軟な引数の指定ができます。が、一つのプログラムあるいはプロジェクトの中でごちゃごちゃに混ぜると混乱するので、ある程度統一することが望ましいと思います。
引数の初期値
- 名前付き引数やオプション位置引数の初期値を”="に続けて記載する。
- デフォルト値はコンパイル時定数でなければならない。
- デフォルト値が指定されないとnullになる
- ただし、nullが使えない型の場合はエラーになる
- 古いコードでは"="ではなく":"を使っている場合がある。将来廃止されるかもしれないので"="を使うことを推奨する。
- 引数にlistやmapを初期値にすることができる。
以下はオリジナルのツアーにあるサンプルを少し変えて実行できる様にしたものです。正直、わかりにくい気がします。
main()関数
- 全てのアプリはトップレベルにmain()関数が必要で、そこがアプリの実行開始点となる。
- main()関数はvoidを返し、任意のパラメータList<String>を持つ。
- コマンドライン引数を解析するにはargsライブラリを使用することができる。
第1級クラスオブジェクトとしての関数
- 関数を他の関数の引数として渡すことができる。
以下はツアーにあるサンプルですが、list.forEachの引数として、関数を渡しています。
List<int>.forEachで指定した関数には引数int elementとして、リストの各要素を順番に渡されるので、関数内のprintによってリストの要素が出力されます。
無名関数
- 関数には名前があるが、無名の関数を作成できる。
- ラムダとかクロージャとも呼ばれる。
- 無名関数を変数に代入することもできる。
- 引数の指定は名前付きの関数と同様である。
上のサンプルは無名関数を使って、以下の様に書くことができます。
静的スコープ
- Dartは静的スコープであり、変数のスコープはコードのレイアウトで決まる。
- 変数がスコープ内かどうかは中括弧を外側に辿ることで確認できる。
いかにもサンプルなものですが、要は定義されているところの直上の中括弧で囲まれた範囲全体が変数の有効範囲です。
もうちょっと現実的な話です。
ネスト関数にするまでもなく、中括弧で囲むことで別スコープと見做されるのは他の言語と同様です。その場合、その内と外は別世界と見做されるため、同じ名前の変数を宣言、使用しても内側で外側の変数を上書きせずに別物扱いとなります。
こういう明から様な例は実際にはあまり使わないので問題ないと思いますが、for文とかも同様なので注意が必要です。for文で"var n"と宣言しているので、別物となります。逆に"var"を削除して実行すると外側のprintは5になります。
静的クロージャ
- クロージャは元のスコープの外で使用されても、宣言時のスコープ内の変数にアクセスできる関数オブジェクトである。
- 関数は周囲のスコープで定義された変数を閉じ込めることができる。
2ポチ目の意味ですが、DeepLとかで翻訳をしても「周囲のスコープで定義された変数をクローズすることができる」となってやや意味不明かと思います。"close over"には「〜を覆い隠す、閉じ込める」とか「四方から襲い掛かる」という意味があります。後者は文脈に合わないので、ここでは閉じ込めるとしています。
以下がツアーのサンプルを元に。説明のために少しいじっています。
3〜5行目の関数makeAdderで引数にaddByというint値を指定しています。この関数では引数iを持ち、引数addByにiを加えた数値を返す関数を返します。"+="なのでaddByにはiを加算した数値が再代入されています。
9行目と10行目ではmakeAdder関数で2と4を指定して呼び出しているので、それぞれ加算する関数を代入しますが、それぞれのaddByの値は別物です。つまり、無名関数の外側で定義された変数addByを自分の中に持っており、それぞれのaddByは別々に管理している(関数の中に閉じ込めている)ということになります。
14、15行目でそれぞれ引数に3を指定して呼び出しているので、それぞれの関数の返り値は2と4に3を足した5と7になります。この時、addByは加算した数値で更新されているため、これも5と7になります。
さらに17、18行目で引数に4を指定して呼び出しているので、今度は5と7に4を加算した9と11を返し、addByの値もそれに更新されます。
関数の等価性テスト
ツアーのオリジナルではサンプルだけであまり説明もないですが、ある関数を代入した変数と、その関数自体の比較は等価です。ただ、同じクラスの非staticなメソッド同士は別のインスタンスなので等価ではありません。
返り値
- 全ての関数は値を返す。
- 戻り値がない場合、暗黙で"return nulll;"が関数本体に追加される。
そうは言っても、戻り値の型にvoidを指定すると何も値を返さないではないか、とツッコミたくなります。voidを指定した関数の戻り値を変数に代入しようとすると「This expression has a type of 'void’ so its value can’t be used.」と怒られます。
次回は演算子です。
ディスカッション
コメント一覧
まだ、コメントがありません