はじめに
前回(第2回)では制御構文を学びました。今回は 関数 を扱います。
関数はコードを再利用可能な単位にまとめる仕組みです。Flutter の Widget を書くうえでも関数の理解は不可欠です。
この記事で学ぶこと
- 関数の基本定義(戻り値の型、引数)
-
void関数 - アロー構文
=> - 名前付き引数(named parameters)
- 位置引数とオプション位置引数
- デフォルト値
- 関数を変数に代入(
Function型、typedef) - 無名関数(ラムダ式)
- 高階関数(
map,where,forEach)
コード例はすべて DartPad で実行できます。
1. 関数の基本定義
戻り値のある関数
int add(int a, int b) {
return a + b;
}
double calculateBmi(double weightKg, double heightM) {
return weightKg / (heightM * heightM);
}
void main() {
int result = add(3, 5);
print('3 + 5 = $result'); // 3 + 5 = 8
double bmi = calculateBmi(65.0, 1.70);
print('BMI: ${bmi.toStringAsFixed(1)}'); // BMI: 22.5
// 検算: 65.0 / (1.70 * 1.70) = 65.0 / 2.89 = 22.49134...
}
void 関数(戻り値なし)
戻り値がない関数は void を指定します。
void greet(String name) {
print('こんにちは、$name さん!');
}
void printSeparator() {
print('-------------------');
}
void main() {
greet('太郎'); // こんにちは、太郎 さん!
printSeparator(); // -------------------
greet('花子'); // こんにちは、花子 さん!
printSeparator(); // -------------------
}
複数の処理をまとめる
void printScoreReport(String name, int japanese, int math, int english) {
int total = japanese + math + english;
double average = total / 3;
print('=== $name の成績 ===');
print('国語: $japanese 点');
print('数学: $math 点');
print('英語: $english 点');
print('合計: $total 点');
print('平均: ${average.toStringAsFixed(1)} 点');
print('');
}
void main() {
printScoreReport('田中', 85, 92, 78);
// === 田中 の成績 ===
// 国語: 85 点
// 数学: 92 点
// 英語: 78 点
// 合計: 255 点
// 平均: 85.0 点
printScoreReport('佐藤', 72, 68, 91);
// === 佐藤 の成績 ===
// 国語: 72 点
// 数学: 68 点
// 英語: 91 点
// 合計: 231 点
// 平均: 77.0 点
}
2. アロー構文 =>
関数本体が 単一の式 で構成される場合、=> を使って簡潔に書けます。=> 式 は { return 式; } と等価です。
// 通常の書き方
int add(int a, int b) {
return a + b;
}
// アロー構文
int addArrow(int a, int b) => a + b;
// 比較的複雑な式でも1つの式なら使える
String getGrade(int score) =>
score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'D';
bool isEven(int n) => n % 2 == 0;
double circleArea(double radius) => 3.14159265358979 * radius * radius;
void main() {
print(add(3, 5)); // 8
print(addArrow(3, 5)); // 8
print(getGrade(85)); // B
print(isEven(4)); // true
print(isEven(7)); // false
print(circleArea(5.0)); // 78.53981633974483
// 検算: 3.14159265358979 * 25.0 ≒ 78.5398...(浮動小数点演算のため末尾の桁に誤差が生じることがあります)
}
void 関数でのアロー構文
void 関数でもアロー構文を使えます。
void sayHello(String name) => print('Hello, $name!');
void main() {
sayHello('Dart'); // Hello, Dart!
}
3. 名前付き引数(named parameters)
中括弧 {} で囲むと 名前付き引数 になります。呼び出し時に 引数名: 値 の形式で指定します。
基本の名前付き引数
void createUser({required String name, required int age, String role = 'member'}) {
print('名前: $name, 年齢: $age, 役割: $role');
}
void main() {
// 名前付き引数は順序を自由に変えられる
createUser(name: '太郎', age: 25);
// 名前: 太郎, 年齢: 25, 役割: member
createUser(age: 30, name: '花子', role: 'admin');
// 名前: 花子, 年齢: 30, 役割: admin
}
required キーワード
required を付けると、その引数の指定が必須になります。付けない場合はデフォルト値が必要です。
// required なし → デフォルト値が必要
void greet({String name = 'ゲスト', String greeting = 'こんにちは'}) {
print('$greeting、$name さん!');
}
// required あり → 呼び出し時に必ず指定
void sendEmail({required String to, required String subject, String body = ''}) {
print('To: $to');
print('件名: $subject');
print('本文: $body');
}
void main() {
greet(); // こんにちは、ゲスト さん!
greet(name: '太郎'); // こんにちは、太郎 さん!
greet(greeting: 'おはよう', name: '花子'); // おはよう、花子 さん!
sendEmail(to: 'test@example.com', subject: 'テスト');
// To: test@example.com
// 件名: テスト
// 本文:
}
Flutter での実用例
Flutter の Widget は名前付き引数を多用します。以下は概念的な例です。
// Flutter の Widget 的な関数の例(実際のFlutterコードに近い形)
void buildButton({
required String text,
required void Function() onPressed,
double width = 200.0,
double height = 48.0,
String color = 'blue',
}) {
print('Button: "$text" (${width}x$height, $color)');
onPressed();
}
void main() {
buildButton(
text: '送信',
onPressed: () => print('ボタンが押されました'),
color: 'green',
);
// Button: "送信" (200.0x48.0, green)
// ボタンが押されました
}
4. 位置引数とオプション位置引数
通常の位置引数(必須)
String formatName(String firstName, String lastName) {
return '$lastName $firstName';
}
void main() {
print(formatName('太郎', '田中')); // 田中 太郎
}
オプション位置引数 [...]
角括弧 [] で囲むとオプション(省略可能)になります。
String introduce(String name, [int? age, String? hobby]) {
String result = '名前: $name';
if (age != null) {
result += ', 年齢: $age';
}
if (hobby != null) {
result += ', 趣味: $hobby';
}
return result;
}
void main() {
print(introduce('太郎')); // 名前: 太郎
print(introduce('花子', 28)); // 名前: 花子, 年齢: 28
print(introduce('次郎', 30, 'プログラミング')); // 名前: 次郎, 年齢: 30, 趣味: プログラミング
}
オプション位置引数のデフォルト値
String repeat(String text, [int times = 1, String separator = ' ']) {
List<String> parts = [];
for (int i = 0; i < times; i++) {
parts.add(text);
}
return parts.join(separator);
}
void main() {
print(repeat('Hello')); // Hello
print(repeat('Hello', 3)); // Hello Hello Hello
print(repeat('Dart', 3, '-')); // Dart-Dart-Dart
}
名前付き引数 vs オプション位置引数
| 特徴 | 名前付き引数 {...}
|
オプション位置引数 [...]
|
|---|---|---|
| 呼び出し時 | name: value |
順番通りに値を渡す |
| 順序 | 自由 | 固定 |
| 途中の省略 | 可能 | 不可(前の引数を省略できない) |
| Flutter での使用 | 非常に多い | 比較的少ない |
Flutter の Widget では名前付き引数が圧倒的に多く使われます。引数の意図が明確になるためです。
5. デフォルト値
名前付き引数・オプション位置引数の両方でデフォルト値を指定できます。
// 名前付き引数のデフォルト値
void printConfig({
String host = 'localhost',
int port = 8080,
bool debug = false,
}) {
print('Host: $host');
print('Port: $port');
print('Debug: $debug');
}
// オプション位置引数のデフォルト値
double calculateTax(int price, [double taxRate = 0.10]) {
return price * taxRate;
}
void main() {
print('--- デフォルト設定 ---');
printConfig();
// Host: localhost
// Port: 8080
// Debug: false
print('\n--- カスタム設定 ---');
printConfig(host: '192.168.1.1', port: 3000, debug: true);
// Host: 192.168.1.1
// Port: 3000
// Debug: true
print('\n--- 税計算 ---');
print('税額(10%): ${calculateTax(1000)}'); // 税額(10%): 100.0
print('税額(8%): ${calculateTax(1000, 0.08)}'); // 税額(8%): 80.0
}
6. 関数を変数に代入する
Dart では関数はファーストクラスオブジェクトです。変数に代入したり、引数として渡したりできます。
Function 型
void main() {
// 関数を変数に代入
int Function(int, int) operation = add;
print(operation(3, 5)); // 8
operation = multiply;
print(operation(3, 5)); // 15
// var でも型推論される
var greetFunc = greet;
greetFunc('太郎'); // Hello, 太郎!
}
int add(int a, int b) => a + b;
int multiply(int a, int b) => a * b;
void greet(String name) => print('Hello, $name!');
typedef で関数型に名前を付ける
typedef MathOperation = int Function(int, int);
typedef StringFormatter = String Function(String);
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;
String toUpperCase(String s) => s.toUpperCase();
String addExclamation(String s) => '$s!';
// typedef を使って関数を引数として受け取る
void performCalculation(MathOperation op, int a, int b) {
print('結果: ${op(a, b)}');
}
String applyFormat(StringFormatter formatter, String text) {
return formatter(text);
}
void main() {
performCalculation(add, 10, 3); // 結果: 13
performCalculation(subtract, 10, 3); // 結果: 7
performCalculation(multiply, 10, 3); // 結果: 30
print(applyFormat(toUpperCase, 'hello')); // HELLO
print(applyFormat(addExclamation, 'hello')); // hello!
}
7. 無名関数(ラムダ式)
名前を持たない関数を 無名関数(anonymous function)と呼びます。一時的に使う関数に適しています。
基本構文
void main() {
// 無名関数を変数に代入
var square = (int n) {
return n * n;
};
print(square(5)); // 25
print(square(9)); // 81
// アロー構文でさらに簡潔に
var cube = (int n) => n * n * n;
print(cube(3)); // 27
print(cube(4)); // 64
}
コレクションのメソッドに渡す
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// forEach に無名関数を渡す
numbers.forEach((n) {
print('値: $n');
});
// 値: 1
// 値: 2
// 値: 3
// 値: 4
// 値: 5
// アロー構文で簡潔に
numbers.forEach((n) => print('2倍: ${n * 2}'));
// 2倍: 2
// 2倍: 4
// 2倍: 6
// 2倍: 8
// 2倍: 10
}
8. 高階関数
高階関数 とは、関数を引数として受け取る、または関数を戻り値として返す関数です。Dart のコレクション型には多くの高階関数が用意されています。
map ― 各要素を変換する
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// 各要素を2倍にする
Iterable<int> doubled = numbers.map((n) => n * 2);
print(doubled.toList()); // [2, 4, 6, 8, 10]
// 各要素を文字列に変換
Iterable<String> strings = numbers.map((n) => '第${n}位');
print(strings.toList()); // [第1位, 第2位, 第3位, 第4位, 第5位]
// 名前のリストを加工
List<String> names = ['taro', 'hanako', 'jiro'];
List<String> formatted = names
.map((name) => name[0].toUpperCase() + name.substring(1))
.toList();
print(formatted); // [Taro, Hanako, Jiro]
}
注意:
map()はIterableを返します。Listが必要な場合は.toList()を呼びます。
where ― 条件で絞り込む
void main() {
List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 偶数だけを抽出
List<int> evens = numbers.where((n) => n % 2 == 0).toList();
print('偶数: $evens'); // 偶数: [2, 4, 6, 8, 10]
// 5 より大きい値を抽出
List<int> greaterThan5 = numbers.where((n) => n > 5).toList();
print('5より大きい: $greaterThan5'); // 5より大きい: [6, 7, 8, 9, 10]
// 3 の倍数を抽出
List<int> multiplesOf3 = numbers.where((n) => n % 3 == 0).toList();
print('3の倍数: $multiplesOf3'); // 3の倍数: [3, 6, 9]
}
forEach ― 各要素に処理を適用
void main() {
List<String> fruits = ['りんご', 'バナナ', 'みかん'];
fruits.forEach((fruit) => print('果物: $fruit'));
// 果物: りんご
// 果物: バナナ
// 果物: みかん
// インデックス付きで処理したい場合は asMap() を使う
fruits.asMap().forEach((index, fruit) {
print('${index + 1}. $fruit');
});
// 1. りんご
// 2. バナナ
// 3. みかん
}
メソッドチェーンで組み合わせる
高階関数はメソッドチェーンで組み合わせると強力です。
void main() {
List<int> scores = [45, 82, 67, 93, 58, 76, 88, 41, 95, 70];
// 80点以上のスコアを降順で表示
List<int> topScores = scores
.where((s) => s >= 80) // 80点以上を抽出
.toList()
..sort((a, b) => b.compareTo(a)); // 降順にソート
print('80点以上(降順): $topScores');
// 80点以上(降順): [95, 93, 88, 82]
// 各スコアを10点満点に変換して表示
List<double> normalized = scores
.map((s) => s / 10.0)
.toList();
print('10点満点換算: $normalized');
// 10点満点換算: [4.5, 8.2, 6.7, 9.3, 5.8, 7.6, 8.8, 4.1, 9.5, 7.0]
}
自作の高階関数
// 関数を引数として受け取る高階関数
List<int> applyToAll(List<int> numbers, int Function(int) transform) {
List<int> result = [];
for (int n in numbers) {
result.add(transform(n));
}
return result;
}
// 関数を返す高階関数
int Function(int) createMultiplier(int factor) {
return (int n) => n * factor;
}
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// 自作高階関数に無名関数を渡す
List<int> squared = applyToAll(numbers, (n) => n * n);
print('二乗: $squared'); // 二乗: [1, 4, 9, 16, 25]
List<int> incremented = applyToAll(numbers, (n) => n + 10);
print('+10: $incremented'); // +10: [11, 12, 13, 14, 15]
// 関数を返す高階関数
var triple = createMultiplier(3);
var tenTimes = createMultiplier(10);
print(triple(7)); // 21
print(tenTimes(7)); // 70
List<int> tripled = applyToAll(numbers, triple);
print('3倍: $tripled'); // 3倍: [3, 6, 9, 12, 15]
}
9. 総合的なコード例
ここまでの内容を組み合わせた実践的な例です。
typedef Validator = bool Function(String);
// バリデーション関数
bool isNotEmpty(String value) => value.isNotEmpty;
bool isValidEmail(String value) => value.contains('@') && value.contains('.');
bool isMinLength(String value) => value.length >= 8;
// バリデーションを実行する高階関数
Map<String, bool> validate(String value, Map<String, Validator> rules) {
Map<String, bool> results = {};
rules.forEach((ruleName, validator) {
results[ruleName] = validator(value);
});
return results;
}
void printValidationResult(String fieldName, String value, Map<String, bool> results) {
print('=== $fieldName: "$value" ===');
results.forEach((rule, passed) {
String status = passed ? 'OK' : 'NG';
print(' $rule: $status');
});
print('');
}
void main() {
Map<String, Validator> emailRules = {
'空でないこと': isNotEmpty,
'メール形式': isValidEmail,
};
Map<String, Validator> passwordRules = {
'空でないこと': isNotEmpty,
'8文字以上': isMinLength,
};
// メールのバリデーション
String email = 'user@example.com';
Map<String, bool> emailResult = validate(email, emailRules);
printValidationResult('メール', email, emailResult);
// === メール: "user@example.com" ===
// 空でないこと: OK
// メール形式: OK
// パスワードのバリデーション
String password = 'abc';
Map<String, bool> passwordResult = validate(password, passwordRules);
printValidationResult('パスワード', password, passwordResult);
// === パスワード: "abc" ===
// 空でないこと: OK
// 8文字以上: NG
// 空文字のバリデーション
String empty = '';
Map<String, bool> emptyResult = validate(empty, emailRules);
printValidationResult('空入力', empty, emptyResult);
// === 空入力: "" ===
// 空でないこと: NG
// メール形式: NG
}
練習問題
練習問題 1: 温度変換関数
以下の2つの関数をアロー構文で作成してください。
-
celsiusToFahrenheit: 摂氏を華氏に変換(華氏 = 摂氏 * 9 / 5 + 32) -
fahrenheitToCelsius: 華氏を摂氏に変換(摂氏 = (華氏 - 32) * 5 / 9)
以下の変換を行ってください: 0度C, 100度C, 212度F, 32度F
期待する出力:
0.0°C → 32.0°F
100.0°C → 212.0°F
212.0°F → 100.0°C
32.0°F → 0.0°C
模範解答
double celsiusToFahrenheit(double celsius) => celsius * 9 / 5 + 32;
double fahrenheitToCelsius(double fahrenheit) => (fahrenheit - 32) * 5 / 9;
void main() {
print('${0.0}°C → ${celsiusToFahrenheit(0.0)}°F');
print('${100.0}°C → ${celsiusToFahrenheit(100.0)}°F');
print('${212.0}°F → ${fahrenheitToCelsius(212.0)}°C');
print('${32.0}°F → ${fahrenheitToCelsius(32.0)}°C');
}
ポイント:
- アロー構文
=>は関数本体が単一の式のときに使えます。 - 検算:
0 * 9 / 5 + 32 = 32.0,100 * 9 / 5 + 32 = 180 + 32 = 212.0,(212 - 32) * 5 / 9 = 180 * 5 / 9 = 100.0,(32 - 32) * 5 / 9 = 0.0
練習問題 2: 名前付き引数で自己紹介
名前付き引数を使った introduce 関数を作成してください。
-
name(必須): 名前 -
age(必須): 年齢 -
hobby(任意、デフォルト:'特になし'): 趣味 -
city(任意、デフォルト:'未設定'): 居住地
期待する出力:
名前: 太郎, 年齢: 25, 趣味: プログラミング, 居住地: 東京
名前: 花子, 年齢: 30, 趣味: 特になし, 居住地: 未設定
名前: 次郎, 年齢: 22, 趣味: 特になし, 居住地: 大阪
模範解答
void introduce({
required String name,
required int age,
String hobby = '特になし',
String city = '未設定',
}) {
print('名前: $name, 年齢: $age, 趣味: $hobby, 居住地: $city');
}
void main() {
introduce(name: '太郎', age: 25, hobby: 'プログラミング', city: '東京');
introduce(name: '花子', age: 30);
introduce(name: '次郎', age: 22, city: '大阪');
}
ポイント: required を付けた引数は省略できません。デフォルト値がある引数は省略可能で、省略した場合はデフォルト値が使われます。名前付き引数は順番を変えて呼び出せるため、可読性が高くなります。
練習問題 3: 高階関数でリスト変換
整数のリスト [12, 5, 28, 3, 17, 9, 21, 14] に対して、以下の操作をメソッドチェーンまたは高階関数を使って行ってください。
- 10以上の値だけを抽出する
- 抽出した各値を2倍にする
- 結果を出力する
期待する出力:
元のリスト: [12, 5, 28, 3, 17, 9, 21, 14]
10以上を抽出して2倍: [24, 56, 34, 42, 28]
模範解答
void main() {
List<int> numbers = [12, 5, 28, 3, 17, 9, 21, 14];
List<int> result = numbers
.where((n) => n >= 10)
.map((n) => n * 2)
.toList();
print('元のリスト: $numbers');
print('10以上を抽出して2倍: $result');
}
ポイント:
-
whereで条件に合う要素を絞り込み、mapで各要素を変換します。 - 検算: 10以上は
[12, 28, 17, 21, 14]→ 2倍で[24, 56, 34, 42, 28] - メソッドチェーンにより、一連の処理を宣言的に記述できます。
まとめ
| 項目 | 内容 |
|---|---|
| 基本定義 | 戻り値型 関数名(引数) { ... } |
| void 関数 | 戻り値なし。void 関数名(引数) { ... }
|
| アロー構文 |
=> 式 で { return 式; } を簡潔に |
| 名前付き引数 | {required String name, int age = 0} |
| オプション位置引数 | [int? age, String name = 'default'] |
| デフォルト値 | 名前付き・オプション位置引数の両方で指定可 |
| 関数の変数代入 | int Function(int, int) op = add; |
| typedef | typedef MathOp = int Function(int, int); |
| 無名関数 |
(引数) { ... } または (引数) => 式
|
| map | 各要素を変換して新しいコレクションを生成 |
| where | 条件に合う要素を抽出 |
| forEach | 各要素に対して処理を実行 |
この3回で Dart の基礎文法(変数・制御構文・関数)を一通り学びました。次のステップとして、クラスとオブジェクト指向の学習に進むことをお勧めします。
参考
- Dart Language Tour - Functions
- Dart Language Tour - Typedefs
- Effective Dart: Usage
- Dart API - Iterable class
- DartPad
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!