Flutter & Dart Cookbookの読書メモ(第3章)

Implementing Functions
この章では、Dartの基本を越えて、関数を紹介します。お気づきのように、私たちはすでに多くの関数を使用してきました(例えば、mainやprintなど)。この章では、関数を使用する主なケースを探ります。
より複雑なアプリケーションを構築するためには、開発者は単純な構成から進歩することが必要です。最低限、いくつかの基本的な概念とアルゴリズムについて理解していることが望まれます。この章では、コード・アイソレーションの基礎を学びます。
関数の主な用途は、命令をグループ化することです。この章では、まず、パラメータや戻り値を持たない基本的な関数を定義する方法について説明します。ほとんどの場合、これは使いたいパターンではありません。しかし、学習目的のために、このパターンが含まれています。この後、パラメータと戻り値について学びます。この時点で、なぜパラメータと戻り値を追加することが強力で、望ましいパターンであるのかが明らかになることを期待しています。
この章の終わりには、関数の他の使い方の例も出てきます。開発者としてのスキルが上がれば上がるほど、関数の使い方も変わってくることがわかるでしょう。

3.1 Declaring Functions
- Problem
特定のタスクを実行する命令には共通のグループ名が必要です。
- Solution
タスクの指示を集約するための関数を宣言する。次の例では、getCurrentDateTime 関数を使用して日付/時刻の値を出力しています。
void main() {
getCurrentDateTime();
}
void getCurrentDateTime() {
var timeLondon = DateTime.now();
print('London: $timeLondon');
}
Dartでは、アプリケーションの起点としてmain()関数を使用します。この定型的なコードは、何の取り決めもなく、戻り値も期待しません(つまり、void)。この関数から、アプリケーション内の処理を処理するための他の関数を作成したり、呼び出したりすることができます。
この例では、getCurrentDateTime という名前のユーティリティ関数を作成しています。この関数は、パラメータを必要とせず、main関数と同様にvoidの戻り値を返すように宣言されていることに注意してください。getCurrentDateTime関数の役割は、現在のDateTimeを取得することです。そのために、DateTime.now()というライブラリ関数を呼び出して、実行時に現在の時刻を返します。
main関数に直接コードを追加することもできますが、現在の日付にアクセスする方法ができたので、他のプログラムにも簡単に追加することができます。別の関数に機能をまとめたので、これを分離して、他のプログラムで必要に応じて使用することができます。プログラムの中で機能を分離することは、身につけるべき良い習慣であり、時間が経つにつれてコードの多くを再利用できるようになることを意味します。

3.2 Adding Parameters to Functions
- Problem
関数に変数情報を渡したい。 - Solution
パラメータを使用して、関数に情報を渡すことができます。次の例では、制御フローの一部として使用されるパラメータを関数に提供しています。
void main() {
getCurrentDateTime(-7);
}
void getCurrentDateTime(int hourDifference) {
var timeNow = DateTime.now();
var timeDifference = timeNow.add(Duration(hours: hourDifference));
print('The time now is: $timeNow');
print('The time minus 7 hours is: $timeDifference');
}
- Discussion
前述の例では、関数に与えられたパラメータを使用してアクションを決定しています。この関数は、DateTime.now() が返す現在時刻を使用して、特定の時差を決定するために使用されます。
関数にパラメータ値を追加したことで、必要な時間差の数を明記することができるようになりました。これによって、この関数はより多くのユースケースに適用できるようになりました。しかし、この関数は値を返さないので、柔軟性には欠けます。
パラメータを使用すると、変数を追加することで関数の柔軟性を高めることができます。関数シグネチャに変数を追加することで、関数がより一般的になります。このように汎化された関数を作成することは、タスクのために作成する必要があるコードの量を減らすための良いアプローチです。

3.3 Using Optional Parameters
- Problem
関数のパラメータ数を変化させたい。 - Solution
オプションのパラメータを提供する。Dartは、値を省略できるオプションのパラメータをサポートしています。オプションのパラメータには、名前付きと位置指定の 2 種類があります。
以下は、名前付きパラメータの使用例です。
void main() {
printGreetingNamed();
printGreetingNamed(personName: "Rich");
printGreetingNamed(personName: "Mary", clientId: 001);
}
void printGreetingNamed({String personName = 'Stranger', int clientId = 999}) {
if (personName.contains('Stranger')) {
print('Employee: $clientId Stranger danger');
} else {
print('Employee: $clientId $personName');
}
}
ここでは、位置指定パラメータの使用例を紹介します。
void main() {
printGreetingPositional("Rich");
printGreetingPositional("Rich", "Rose");
}
void printGreetingPositional(String personName, [String? personSurname]) {
print(personName);
if (personName != null) {
print(personSurname);
}
}
Dartでは、関数にパラメータを使用する柔軟性がさらに向上しています。パラメータを省略できる場合は、オプションのパラメータを使用することを検討すると便利です。
名前付きパラメータを使用すると、関数宣言の中に名前付き変数を含めることができます。このタイプのパラメータを使用するには、中括弧で囲んで必要な値を定義します。最初の例では、オプションのパラメータを使用して、名前と clientId を渡しています。もし、この関数に情報が与えられなかった場合でも、デフォルトの値として期待通りに動作します。値を提供して特定のロジックを実行する必要がある場合は、デフォルト値を提供することができます。"int clientId = 999 "など。
位置決めパラメータは通常のパラメータと同様に動作し、必要に応じて省略できる柔軟性を持っています。2番目の例では、2番目のパラメータを角括弧を使用して位置決めパラメータとして定義しています。さらに、Dartでは、?文字を含めることで、変数をNULL値の可能性があるものとして定義することができます。
名前付きパラメータと位置決めパラメータの両方が、関数に柔軟性をもたらします。パラメータが必要なさまざまなシナリオで使用できます(たとえば、人物オブジェクトで姓と名は必須だが、ミドルネームは任意という場合など)。

3.4 Returning Values from Functions
- Problem
計算値を返す命令には、共通のグループ名が必要です。 - Solution
値を計算し、これを呼び出し側のメソッドに返す名前付き関数を使用します。これは、命令をグループ化するための一般的なメカニズムです。
以下は、値を返す関数を宣言する例です。
void main() {
DateTime timeNow = getCurrentDateTime(0);
DateTime timeDifference = getCurrentDateTime(-7);
print('The time now is: $timeNow');
print('The time minus 7 hours is: $timeDifference');
}
DateTime getCurrentDateTime(int hourDifference) {
DateTime timeNow = DateTime.now();
DateTime timeDifference = timeNow.add(Duration(hours: hourDifference));
return timeDifference;
}
先の例では、関数がパラメータを受け付けるということは、異なる時間値を提供できることを意味します。この関数は、パラメータを受け取り、値を返すと宣言されています。これで、より汎用的な関数ができ、より幅広い一連の設定で活用できるようになりました。
この例では、パラメータを受け取る関数が、異なる時間の値を提供できることを意味します。この関数が知っているのは、使用する時間数を表す int 値を受け取ることだけです。この例では、2回関数を呼び出して、現在時刻を0時間で初期化し、次に差分として7時間を与えています。
getCurrentDateTime関数の戻り値は、DateTimeオブジェクトです。この戻り値をキャプチャすることで、相対的な日付時刻の組み合わせを出力することができます。
整数のパラメータで呼び出され、DateTimeオブジェクトを返す関数を再利用していることに注意してください。DateTimeのようなクラスオブジェクトに関連する豊富なメソッドセットを持つことで、膨大な開発時間を節約することができ、Dartはこのようなシンプルな機能を作成する機会を提供します。

3.5 Declaring Anonymous Functions
- Problem
関数の中で式を囲みたい。 - Solution
簡単な式を実行するための無名関数を宣言する。関数が単一の式しか必要としないことがよくありますが、その場合、無名関数はエレガントな解決策を提供することができます。
ここでは、無名関数の使用例について説明します。
void main() {
int value = 5;
// Anonymous Function - Style 1
int ex1Squared(num1) => num1 * num1;
int ex1Cubed(num1) => num1 * num1 * num1;
// Anonymous Function - Style 2
int ex2Squared(num1) { return num1 * num1; }
int ex2Cubed(num1) { return num1 * num1 * num1; }
print('Ex1: $value squared is ${ex1Squared(value)}');
print('Ex1: $value cube is ${ex1Cubed(value)}');
print('Ex2: $value squared is ${ex2Squared(value)}');
print('Ex2: $value cube is ${ex2Cubed(value)}');
}
この例では、必要に応じて数値を二乗/三乗する関数が作成されています。このアルゴリズムは入力を受け取り、それを掛け算して呼び出し元に結果を返します。この関数には名前がなく、匿名であることに注意してください。通常、無名関数は値を返すための短いコードに使われることが多い。関数の前に、関数からの結果を格納するための変数が宣言されています。注意:変数にパラメータを含めるには、括弧の中でパラメータを追加します。
匿名関数は、通常、関数を示すために => を使用します。この例では、最初に宣言された関数がこのスタイルを使っている。このタイプの宣言では、関数の戻り値は暗黙の了解となっており、実行した式の結果にアクセスすることができます。
2番目のスタイルは、同じタスクを実行します。しかし、結果へのアクセスを提供するために、明示的なreturnが使用されていることに注意してください。どちらの形式を選択するかは、開発者次第です。
Dartはファーストクラス関数を使用できます。これは基本的に、関数(引数として渡される)とデータ構造を組み合わせて使用できることを意味します。この例では、print文が埋め込まれた関数を処理して、結果を取得できることに注目してください。他の使用例としては、繰り返しごとに関数を呼び出すforEachループの一部として使用することができます。

3.6 Adding a Function Delay Using a Future
- Problem
完了状態を待つカスタムディレイを導入したい。 - Solution
Futureを使用して、コードの中で指定されたプログラム上の遅延を実行します。以下は、Futureを使用してプログラム的な遅延を実現する例です。
void main() async {
int myDelay = 5;
print('Hello');
var value = await _customDelay(myDelay);
var customText = myDelay == 1 ? "second later" : "seconds later";
print('Its $value $customText');
}
Future<int> _customDelay(int delay) async {
try {
await Future.delayed(Duration(seconds: delay));
return delay;
} catch(e) {
print(e);
return delay;
}
}
この例では、main関数で指定した数値に基づき、カスタムディレイを実装します。customDelayの呼び出しは、2つの状態(例えば、完了と未完了)を特徴とする非同期処理であるFutureを使用します。非同期操作は、通常、他の操作が終了するのを待つために使用されます。この例では、遅延の長さを示す最終メッセージを出力する前に、プログラムを遅延させたいと考えています。
Futureクラスは様々な場面で使用され、典型的には完了までに少し時間がかかるような、より長く実行される処理をロードするために使用されます。第13章では、Futureクラスは特にリモートデータアクセスで使用されます。
Futureを使用する際、その内部構造を示す2つのものを見ることができます。1つ目はasyncへの言及で、これは非同期関数が使用されていることを示します。さらに、awaitキーワードへの言及も見られますが、これは「この時点では、続行する前に応答を待ってください」という意味です。このコードでは、非同期式の完了状態を待つことになります。