0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter入門 第3回】Dart の関数 ― 定義・引数・アロー構文・高階関数

0
Posted at

はじめに

前回(第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] に対して、以下の操作をメソッドチェーンまたは高階関数を使って行ってください。

  1. 10以上の値だけを抽出する
  2. 抽出した各値を2倍にする
  3. 結果を出力する

期待する出力:

元のリスト: [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 の基礎文法(変数・制御構文・関数)を一通り学びました。次のステップとして、クラスとオブジェクト指向の学習に進むことをお勧めします。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?