はじめに
ここではDartの様々な関数表現についてまとめていきます。
関数と言えばy=f(x)の基本形、パラメタxをとってyを返すというものですね。コンピュータ・サイエンスの計算基礎論のレベルでこう扱われているので、これに関してはどのプログラミング言語も同じです。
抑えておきたいポイントは、パラメタの渡し方に様々な形式があるところです。これを駆使してFlutterでのウィジェットの定義を表現していきます。
TextField( maxLength: 50, maxLines:1, onChanged: (String txt)=> value = txt, ),
これはFlutterでのTextFieldのパーツを表記した例です。なにかJSONに代表される構造的データ記述言語のような雰囲気ですが、後ほど後述する「選択的パラメタ渡し」や「オブジェクトとしての関数」の仕組みが使われています。
もし選択的パラメタ渡しの仕組みがなければ:
TextField(0, null, 0, 0, 50, 0, null, null, 1, null, "", .....)
みたいに、何番目のパラメタが何だから何番目にどの数字を入れて、使わないところは0とかnullで埋めて、などという地獄絵図になってしまいます。
またしっかり関数表記の理解は、見様見真似のコピペプログラミングから脱却して未知のAPIの使い方をドキュメントから理解する道具になるという点で重要です。
関数の表記
様々な関数関連の表記を見ていきます。
基本形
基本の形は以下の通りです:
int inclement(int a){ return a+1; }
上記ではint型のパラメタを1つとって、int型の値を1つ返します。
関数の計算が「1つの式」で完結する場合はアロー表記が使えます:
int inclement(int a) => a+1;
上の2つは同じ意味の関数になります。
オブジェクトとしての関数
関数自体も通常のオブジェクトとして扱うことができ、変数の値として受け渡すことができます。
例えば下記の関数定義があるとします:
//関数定義 int inclement(int a){ return a+1; }
ここで関数名「inclement」は変数名のように関数自体を表す名前として扱うことができます:
void main(){ //calcは「intを1つ引数にとりint型を返す関数」型 //その値として上記の関数を代入 int Function(int) calc = inclement; print(calc(2)); //3が出力される }
上記コードの「int Function(int)」が変数calcの型です。この型は「戻り値型 Function(パラメタの型の列)」という形をしています。関数の形を表しているわけですね。なのでその型に合致する関数inclementを代入できます。
型推論は効きますので、下記のようにvarやfinalで宣言しても大丈夫です:
//関数定義 int inclement(int a){ return a+1; } void main(){ var calc = inclement; //これでもOK print(calc(2)); //3が出力される }
名前無し関数
関数は必ずしも名前を付ける必要はなく、パラメタ列と返り値型と本体の計算だけで定義することができます。「int a = 10;」という表記があったときに「a」という名前がなくても「10」という値の表記をどこでも使えるのと同様です。
名前をつけるというのは、基本的に:
- 設計の意図を表現する
- 複数回使う
ということを目的にしています。特に同一であるべきものを複数回記述するのはよくないので、こういうときに名前をつけます。逆に一度きり、その場でしか登場しない値や関数については、必ずしも名前をつける必要はありません。
Flutterは関数をパラメタの値としてとる仕組みを多用します。表記の形としては:
- 一行表記:(パラメタ列)=>式
- 複数文表記:(パラメタ列){ ... }
の二種類があります。名前あり関数定義の、戻り値型と名前を省いた形というだけですね。
//一行表記 final f = (int a)=>a+1; //複数行表記 final f = (int a){ var ret = a+1; return ret; };
一番良く見られるケースは、下記のような状況です:
void main() { final names = ["Yaris", "Aqua","Prius"]; //namesのすべての値に関数を適用 names.forEach((String s)=>print(s.length)); }
forEachはコレクションのそれぞれの要素に与えられた関数を適用していきます。namesがString型のリストですので、forEachは「void Function(String)」の形のパラメタをとります。Stringが引数で返り値がない(void型)の関数です。
パラメタが必要ない場合は「_」を表記することで伏せられます。
void main() { final names = ["Yaris", "Aqua","Prius"]; //必要のないパラメタを伏せる names.forEach((_)=>print("hey")); //これは型が違うのでコンパイルが通らない names.forEach(()=>print("hey")); }
パラメタが要求されている、ということは通常の使い方ではある程度そのパラメタは必要だろうという設計の意図があります。それでも例外的にいまは必要がない、テスト的な実行なので今はそのパラメタを使わない、という場合にパラメタを伏せます。
選択的パラメタ渡し
関数が指定する引数をすべて指定するのではなく、選択的に値を渡し、それ以外は関数側で指定したデフォルト値、あるいはnullが指定されるという仕組みがあります。
冒頭で少し例を挙げましたが、Flutterで多用される仕組みです。
void optFunc({int a=0, int b=3}){ print(a+b); } void main() { optFunc(a: 1); //4 optFunc(b: 5); //5 optFunc(a: 4, b:5); //9 optFunc(b:5, a:4); //順番を変えてもOK }
上記の関数optFuncの引数定義のところが{}で囲まれています。{}で囲むとその部分は選択的な引数であることを示し、必ずしもその値を指定しなくてよいことを示します。
値が指定されなかった場合のデフォルト値はa=0, b=3のように引数列に記載します。
呼び出し側の値の指定はmain関数内のoptFuncの呼び出しにあるとおり、仮引数名+コロン(:)で行います。
requiredを引数の型の前につけると、その引数の指定は必須になります。
void optFunc({int a=0, required int b}){ print(a+b); } void main() { optFunc(a: 1); //bの指定がないのでNG optFunc(b: 5); //aは省略可能なのでOK optFunc(a: 4, b:5); //OK optFunc(b:5, a:4); //OK }
後ろ側の一部の引数のみを選択的にするのもOKです:
void optFunc(int a, {required int b, int c=3}){ print(a+b+c); } void main() { opuFunc(b:2); //一番最初のaの値はマスト optFunc(1, b:2); //6 optFunc(2, b:3, c:5); //10 optFunc(a: 4, b:5, c:5); //これはコンパイルできない。aの名前は指定しない optFunc(4, b:5, c:5); //14 }
位置指定パラメタ渡し
名前を指定した選択的パラメタ渡しではなく、位置を固定した形でデフォルト値を設定し、パラメタを省略可能にする方法もあります。名前を指定する方法の関数定義の{}のところを[]に替えるだけでOKです:
void optFunc([int a=1, int b=2]){ print(a+b); } void main() { optFunc(); //3 a, bの双方がデフォルト値 optFunc(3); //5 bのみデフォルト値 optFunc(4, 5); //9 }
typedef
C言語でおなじみのtypedefが使えます。指定した型に別名をつける仕組みです。
これは関数に限ったものではなく、任意の型に別名をつけることができます。ただ関数の型定義などは長くなりがちですので省略記法はありがたいことがよくあります。
//int Function(int, int)型の別名をcalcとする typedef calc = int Function(int, int); //calc型のパラメタを取る関数 void printNum(calc f){ print(f(1, 2)); } void main(){ printNum((int a, int b)=>a+b); //3 printNum((int a, int b)=>a*b); //2 printNum((_, __)=>1); //1 }
typedefの書式がC言語などとは若干異なるので注意です。変数定義のような様式です。
おわりに
関数は機能を切り出して再利用可能にする仕組みとして極めて有用で多数の場面で用いられます。
FlutterではGUIをプログラムコードに直接記述していくスタイルのため、コードが非常に深いブロックの積み重ねになって行きがちです。
こんなときは適切な単位でウィジェットの記述や機能の記述などを関数に切り分けることが重要なテクニックになってきます。再利用を想定していなくても、記載を分割する目的のためだけにも、関数を活用することは十分有用です。