Function

Dart言語では関数もオブジェクトであり、これは他のプログラミング言語とは少し異なる特徴です。

関数は第一級オブジェクト

Dartは完全なオブジェクト指向言語であり、関数そのものがオブジェクト、型は Function となります。

変数に代入可能、他の関数の引数として渡すことも可能;

クラスのインスタンスを関数のように直接呼び出せる

1 標準的な関数定義

// 戻り値・引数型アノテーションを完全に記述した例
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
Code language: Dart (dart)

完全な実行例

// 希ガスの原子番号マップ
final Map<int, String> _nobleGases = {
  2: 'He',
  10: 'Ne',
  18: 'Ar',
  36: 'Kr',
  54: 'Xe',
  86: 'Rn',
};

// 戻り値・引数型アノテーション付き
// intはnull許容ではないため、int?に変更しnull引数に対応
bool isNoble(int? atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void main() {
  print(isNoble(null));       // nullはMapのキーに存在しない → false
  print(isNoble(2));          // 希ガス存在 → true
  print(isNoble(9));          // 存在しない → false
  print(isNoble(86));         // 存在 → true
}
Code language: Dart (dart)
false
true
false
trueCode language: JavaScript (javascript)

2 型注釈の省略

Effective Dartコーディング規約では、外部に公開する公開APIには完全な型を明示することを推奨しています。

内部のみで使用する関数の場合は型を省略できます


final Map<int, String> _nobleGases = {
  2: 'He',
  10: 'Ne',
  18: 'Ar',
  36: 'Kr',
  54: 'Xe',
  86: 'Rn',
};


isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void main() {
  print(isNoble(null));       // nullはMapのキーに存在しない → false
  print(isNoble(2));          // 希ガス存在 → true
  print(isNoble(9));          // 存在しない → false
  print(isNoble(86));         // 存在 → true
}

Code language: Dart (dart)

上記の例のisNoble関数は、戻り値のbool、引数のint?型を省略していますが、正常に動作します。

型を記述しない場合、Dartは自動的に型推論を行います:

  • 引数 atomicNumber:明示的な型なし → 推論型は dynamic(動的型、数値・文字列・nullなど任意の値を渡せる)
  • 戻り値:先頭に型注釈なし → Dartは return 右辺の式から戻り値が bool であると推論するだけで、コードに記載されない
公開APIに型を記載しなければならない理由

可読性:他の開発者が関数を呼び出す際、引数・戻り値の型が一目で把握でき、内部コードを読んで推測する必要がない;

型安全性dynamic 動的型は静的型チェックを無効化するため、型の誤った引数を渡してもコンパイルエラーが発生せず、実行時クラッシュの原因になりやすい;

IDE補完:型注釈を記載するとエディタが自動補完・引数ヒントを表示;型なしの場合IDEは正確な補完を出力できない。

型を省略可能な場面

ファイル内のプライベートな内部コードのみ推奨、関数が同一ファイル内だけで使用され外部から呼び出されない場合。

アロー簡略構文(=>)

関数本体({}内のコード)が1行の戻り式のみの場合、アロー関数で簡略記述できます。

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
Code language: Dart (dart)
  • => expr{ return expr; } と等価;
  • アローとセミコロンの間には式のみ記述可能、ifなどの文は記述不可(三項演算子で代用可能)。
// 希ガス原子番号マップ
final Map<int, String> _nobleGases = {
  2: 'ヘリウム',
  10: 'ネオン',
  18: 'アルゴン',
  36: 'クリプトン',
  54: 'キセノン',
  86: 'ラドン',
};

// アロー簡略関数:1行式を直接返却、return付きブロック関数と等価
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

void main() {
  // 存在する希ガスをテスト
  print(isNoble(2));   // true
  print(isNoble(18));  // true

  // 一般元素をテスト
  print(isNoble(6));   // false(炭素)
  print(isNoble(99));  // false(対応元素なし)
}
Code language: Dart (dart)

Parameters 引数

関数は必須位置引数に続き、名前付き引数 / オプション位置引数のいずれか一方のみを使用可能、混在不可。

引数定義・呼び出し時に末尾カンマ , を記述可能、コード可読性向上に役立つ。

1. 名前付き引数 Named parameters

{} で囲み、既定でオプション;required を付加すると引数渡しが強制される。

1)基本的なnull許容名前付き引数(デフォルト値なし、既定値はnull、型に? が必要)

void enableFlags({bool? bold, bool? hidden}) {}
// 呼び出し時に引数名を明示
enableFlags(bold: true, hidden: false);
Code language: Dart (dart)

完全な例

/// テキストスタイルフラグを有効化:太字・非表示
void enableFlags({bool? bold, bool? hidden}) {
  print('太字:$bold、非表示:$hidden');
}

void main() {
  // 全ての名前付き引数を渡す
  enableFlags(bold: true, hidden: false);

  // 片方の引数のみ渡し、もう一方はnull
  enableFlags(bold: true);

  // hiddenのみ渡す
  enableFlags(hidden: true);

  // 引数を一切渡さない、両方null
  enableFlags();
}
Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
太字:true、非表示:false
太字:true、非表示:null
太字:null、非表示:true
太字:null、非表示:nullCode language: JavaScript (javascript)

2)コンパイル時定数のデフォルト値設定(null許容不要、デフォルト値が存在するため型に?なしでOK)

void enableFlags({bool bold = false, bool hidden = false}) {}
enableFlags(bold: true); // hiddenは自動的にfalseが適用

Code language: Dart (dart)

/// テキストスタイルフラグを有効化:太字・非表示
void enableFlags({bool bold = false, bool hidden = false}) {
  print('太字:$bold、非表示:$hidden');
}

void main() {
  enableFlags();//引数を渡さない場合両方デフォルトfalse 太字:false、非表示:false
}
Code language: Dart (dart)

3)必須強制名前付き引数 required(補足:Flutter コンポーネントコンストラクタで頻繁に使用) 下記はFlutterの例、詳細はFlutter講座で学習可能。

const Scrollbar({super.key, required Widget child});
// required引数にはnull許容型も使用可能
const Scrollbar({super.key, required Widget? child});

Code language: Dart (dart)

Dartでは名前付き引数を末尾に配置する制約はなく、引数リスト内の任意の位置に挿入可能

repeat(times: 2, () {})Code language: CSS (css)
2. オプション位置引数 Optional positional parameters

[] で囲み、引数を渡さない場合既定値null、定数のデフォルト値を設定可能。

// デフォルト値なし、型に?付き
String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) result = '$result with a $device';
  return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');

Code language: Dart (dart)

完全な例

// [String? device] オプション位置引数、デフォルト値なし、未渡し時はnull
String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device'; //deviceがnull以外の場合、文末にデバイス情報を追記
  }
  return result;
}

void main() {
  // 3番目のオプション引数を渡さない
  String res1 = say('Bob', 'Howdy');
  print(res1);

  // 3番目のオプション位置引数を渡す
  String res2 = say('Bob', 'Howdy', 'smoke signal');
  print(res2);

  // オプション引数に明示的にnullを渡す
  String res3 = say('Alice', 'Hi', null);
  print(res3);
}
Code language: Dart (dart)

デフォルト値を設定する場合:

String say(String from, String msg, [String device = 'carrier pigeon']) {
  return '$from says $msg with a $device';
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

Code language: Dart (dart)

完全な例

// オプション位置引数にデフォルト値 carrier pigeon(伝書鳩)を設定
String say(String from, String msg, [String device = 'carrier pigeon']) {
  return '$from says $msg with a $device';
}

void main() {
  // 3番目のオプション引数未渡し、自動的にデフォルト値 carrier pigeon 使用
  String res1 = say('Bob', 'Howdy');
  print(res1); //Bob says Howdy with a carrier pigeon

  // 独自のデバイスを渡し、デフォルト値を上書き
  String res2 = say('Bob', 'Howdy', 'smoke signal');
  print(res2); //Bob says Howdy with a smoke signal

  String res3 = say('Alice', 'Hello', 'mobile phone');
  print(res3); // Alice says Hello with a mobile phone
  
  String res4 = say('Jack', 'Tom', 'website foxdevelop.com');
  print(res4); // Jack says Tom with a page
}
Code language: Dart (dart)

実行結果

Built firstdart:firstdart.
Bob says Howdy with a carrier pigeon
Bob says Howdy with a smoke signal
Alice says Hello with a mobile phone
Jack says Tom with a website foxdevelop.comCode language: JavaScript (javascript)


main () エントリー関数

全てのDartプログラムにはトップレベル main() が必須、プログラム唯一の実行入口となる。

  1. 引数なし基本版
void main() {
  print('Hello, wellcome to foxdevelop.com!');
}

Code language: Dart (dart)
Hello, wellcome to foxdevelop.com!Code language: CSS (css)
  1. コマンドラインプログラム起動引数受け取り
// 実行コマンド:dart run firstdart.dart 1 test
void main(List<String> arguments) {
  print(arguments); // [1, test]
}

Code language: Dart (dart)

複雑なコマンドライン引数解析には公式 args ライブラリを利用可能。

上記のfirstdart.dartは自身のDartファイル名に置き換えてください


第一級オブジェクトとしての関数 First-class objects

1. 関数を引数として渡す

void printElement(int element) => print(element);
var list = [1, 2, 3];
list.forEach(printElement); // 関数を直接渡す

Code language: Dart (dart)

完全な例

void main() {
    void printElement(int element) => print(element);
	var list = [1, 2, 3];
	list.forEach(printElement); // 関数を直接渡す
}
Code language: Dart (dart)

出力結果

1
2
3

2. 無名関数を変数に代入

void main() {
    var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
	print(loudify('hello')); //!!! HELLO !!! 
}
Code language: Dart (dart)

関数型 Function Type

関数変数の型を明示的に宣言可能

書式:戻り値型 Function(仮引数リスト, {名前付き引数})

void greet(String name, {String greeting = 'Hello'}) => print('$greeting $name!');
// 関数型変数を定義
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');

Code language: Dart (dart)

typedef で関数型に別名を付与し、再利用性・可読性を向上可能。

{String greeting}) → 1つの名前付き引数 greeting、型はString

void main() {
    void greet(String name, {String greeting = 'Hello'}) => print('$greeting $name!');
	// 関数型変数を定義
	void Function(String, {String greeting}) g = greet;
	g('Jack', greeting: 'Howdy');
}
Code language: Dart (dart)

出力結果

Howdy Jack!

無名関数(Lambda / Closure クロージャ)

関数名を持たない関数、2種類の記法:

  1. ブロック記法(複数行)
void main() {
    const list = ['apples', 'bananas'];
	var uppercaseList = list.map((item) {
	  return item.toUpperCase();
	}).toList();
	print(uppercaseList);
}
Code language: Dart (dart)

上記コードの (item) { return item.toUpperCase(); }無名関数(lambda)

出力:[APPLES, BANANAS]

  1. アロー簡略記法(1行式)
void main() {
    const list = ['fox', 'develop','foxdevelop.com'];
    var uppercaseList = list.map((item) => item.toUpperCase()).toList();
    uppercaseList.forEach((item) => print('$item: ${item.length}'));
}

Code language: Dart (dart)

出力結果

FOX: 3
DEVELOP: 7
FOXDEVELOP.COM: 14Code language: HTTP (http)

字句スコープ Lexical scope

Dartは静的字句スコープを採用:変数のスコープはコードの波括弧階層で決定、内側スコープから外側の全変数にアクセス可能。

bool topLevel = true;
void main() {
  var insideMain = true;
  void myFunction() {
    var insideFunction = true;
    void nestedFunction() {
      var insideNestedFunction = true;
      // 全てアクセス可能
      assert(topLevel && insideMain && insideFunction && insideNestedFunction);
    }
  }
}

Code language: Dart (dart)

字句クロージャ Lexical closures

クロージャ:関数オブジェクトが外側スコープの変数を捕捉・保持し、元のスコープを離れた後もアクセスできる仕組み。

Function makeAdder(int addBy) {
  // 外部変数 addBy を捕捉
  return (int i) => addBy + i;
}

void main() {
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

Code language: Dart (dart)

上記コードの実行手順を段階的に解説

第一段階:var add2 = makeAdder(2);

  1. makeAdder 実行、addBy = 2 を渡す
  2. 無名関数を作成:(int i) => 2 + i
  3. この関数を変数 add2 に代入、この時点でadd2は関数オブジェクトとなり、本体は(int i) => 2 + i、int型引数iを受け取りi+2を返却する
  4. makeAdder 関数の実行が終了しても、無名関数は addBy=2 を記憶し続ける

第二段階:var add4 = makeAdder(4);

  1. makeAdder を再度実行、addBy = 4 を渡す
  2. 完全に独立した新規無名関数を作成:(int i) => 4 + i
  3. add4 に代入、独自に addBy=4 を保持、add2と干渉しない。つまりaddByはadd2とadd4の2つの関数オブジェクト内にそれぞれ記録され、一方は2、もう一方は4となる
  • makeAdder(2) 実行時、独立したクロージャが生成され、専用に addBy = 2 が紐付けられ add2 に代入;
  • makeAdder(4) 実行時、別の新規クロージャが生成され、専用に addBy = 4 が紐付けられ add4 に代入;
  • add2add4 は完全に独立した関数オブジェクトで、それぞれ捕捉した addBy を私有保持、互いに影響を与えない。

第三段階:add2(3) を呼び出す

内部関数実行:2 + 3 = 5、アサーション成立

第四段階:add4(3) を呼び出す

内部関数実行:4 + 3 = 7、アサーション成立

関数ティアオフ Tear-offs

関数/メソッド呼び出し時に括弧を記述しないとtear-off(軽量クロージャ)が生成され、手書きの無名ラムダの代わりになりコードが簡潔になる。

推奨記法、関数を直接渡すだけでOK。

var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
charCodes.forEach(print); // 関数ティアオフ
charCodes.forEach(buffer.write); // メソッドティアオフ

Code language: Dart (dart)

冗長な記法

charCodes.forEach((code) => print(code));
charCodes.forEach((code) => buffer.write(code));

Code language: Dart (dart)

関数の等価性判定

  • トップレベル関数・静的メソッド:関数名が完全一致する場合 == で真;
  • インスタンスメソッド:同一オブジェクトインスタンスに紐付いたメソッドティアオフのみ等価となる。
void foo() {}
class A {
  static void bar() {}
  void baz() {}
}
void main() {
  var x = foo;
  assert(foo == x);

  x = A.bar;
  assert(A.bar == x);

  var v = A(), w = A(), y = w;
  x = w.baz;
  assert(y.baz == x); // 同一インスタンス、等価
  assert(v.baz != w.baz); // 異なるインスタンス、非等価
}

Code language: Dart (dart)

戻り値 Return values

  1. 全ての関数に戻り値が存在:明示的な return がない場合、暗黙的に return null; が末尾に追加される
foo() {}
assert(foo() == null);

Code language: Dart (dart)
  1. 複数値を返却:Recordレコードタプルを使用
(String, int) foo() {
  return ('something', 42);
}

Code language: Dart (dart)

Getter & Setter アクセサ

全てのプロパティ読み出し・代入は本質的にgetter/setterの呼び出し;通常変数は暗黙的アクセサが自動生成され、手動で明示的に定義することも可能。

メリット:外部からは統一した .プロパティ名 でアクセス、内部ロジックを後から変更しても外部コードの修正が不要。

String _secret = 'Hello';

// Getter 読み出し用
String get secret {
  print('getter呼び出し');
  return _secret.toUpperCase();
}

// Setter 代入用
set secret(String newMsg) {
  print('setter呼び出し');
  if (newMsg.isNotEmpty) _secret = newMsg;
}

void main() {
  print(secret); // 読み出し、getter起動 → HELLO
  secret = 'Dart is fun'; // 代入、setter起動
  print(secret); // DART IS FUN
}

Code language: Dart (dart)

* ジェネレータ Generators

必要に応じて遅延的にシーケンスを生成、同期・非同期の2種類があり yield で値を出力。

  1. 同期ジェネレータ sync*、戻り値型は Iterable
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

Code language: Dart (dart)
  1. 非同期ジェネレータ async*、戻り値型は Stream
Stream<int> asyncNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

Code language: Dart (dart)
  1. 再帰ジェネレータ最適化 yield*(別のジェネレータに処理を委譲、パフォーマンス向上)
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Code language: Dart (dart)

Function

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です