メインコンテンツにスキップ

関数

Dartは真のオブジェクト指向言語であるため、関数もオブジェクトであり、Functionという型を持ちます。これは、関数を変数に代入したり、他の関数に引数として渡したりできることを意味します。また、Dartクラスのインスタンスを関数のように呼び出すこともできます。詳細については、呼び出し可能なオブジェクトを参照してください。

関数の実装例を以下に示します。

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

Effective DartではパブリックAPIの型アノテーションを推奨していますが、型を省略しても関数は機能します。

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

単一の式で構成される関数には、短い構文を使用できます。

dart
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> expr構文は{ return expr; }のショートカットです。=>表記は、アロー構文と呼ばれることもあります。

パラメータ

#

関数には、任意の数の必須位置パラメータを含めることができます。これらは、名前付きパラメータまたはオプションの位置パラメータのいずれか(両方ではない)で続けることができます。

関数に引数を渡す場合や、関数パラメータを定義する場合に、末尾のカンマを使用できます。

名前付きパラメータ

#

名前付きパラメータは、明示的にrequiredとマークされていない限り、オプションです。

関数を定義する場合、名前付きパラメータを指定するには{param1, param2, …}を使用します。デフォルト値を提供しないか、名前付きパラメータをrequiredとマークしない場合、その型はNullableである必要があります。デフォルト値はnullになります。

dart
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool? bold, bool? hidden}) {
  ...
}

関数を呼び出す場合、paramName: valueを使用して名前付き引数を指定できます。たとえば、

dart
enableFlags(bold: true, hidden: false);

null以外の名前付きパラメータのデフォルト値を定義するには、=を使用してデフォルト値を指定します。指定された値はコンパイル時定数である必要があります。たとえば、

dart
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {
  ...
}

// bold will be true; hidden will be false.
enableFlags(bold: true);

代わりに、名前付きパラメータを必須にして、呼び出し元にそのパラメータの値を指定するように要求したい場合は、requiredでアノテートします。

dart
const Scrollbar({super.key, required Widget child});

Scrollbarchild引数を指定せずに作成しようとすると、アナライザーが問題を報告します。

位置引数を最初に配置したい場合がありますが、Dartはそれを要求しません。Dartでは、APIに応じて、引数リストのどこにでも名前付き引数を配置できます。

dart
repeat(times: 2, () {
  ...
});

オプションの位置パラメータ

#

一連の関数パラメータを[]で囲むと、それらはオプションの位置パラメータとしてマークされます。デフォルト値を提供しない場合、それらの型はNullableである必要があります。デフォルト値はnullになります。

dart
String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

ここでは、オプションのパラメータなしでこの関数を呼び出す例を示します。

dart
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

そして、ここでは3番目のパラメータを指定してこの関数を呼び出す例を示します。

dart
assert(
  say('Bob', 'Howdy', 'smoke signal') ==
      'Bob says Howdy with a smoke signal',
);

null以外のオプションの位置パラメータのデフォルト値を定義するには、=を使用してデフォルト値を指定します。指定された値はコンパイル時定数である必要があります。たとえば、

dart
String say(String from, String msg, [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

main()関数

#

すべてのアプリにはトップレベルのmain()関数が必要です。これはアプリのエントリポイントとして機能します。main()関数はvoidを返し、引数用のオプションのList<String>パラメータを持ちます。

これは単純なmain()関数の例です。

dart
void main() {
  print('Hello, World!');
}

これは、引数を受け取るコマンドラインアプリのmain()関数の例です。

args.dart
dart
// Run the app like this: dart run args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

コマンドライン引数を定義および解析するには、argsライブラリを使用できます。

ファーストクラスオブジェクトとしての関数

#

関数を別の関数のパラメータとして渡すことができます。たとえば、

dart
void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

関数を変数に代入することもできます。たとえば、

dart
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

この例では匿名関数を使用しています。詳細については次のセクションを参照してください。

関数型

#

関数の型を指定できます。これは関数型として知られています。関数型は、関数名キーワードFunctionに置き換えることによって、関数宣言ヘッダーから取得されます。さらに、位置パラメータの名前を省略することは許可されていますが、名前付きパラメータの名前は省略できません。たとえば、

dart
void greet(String name, {String greeting = 'Hello'}) =>
    print('$greeting $name!');

// Store `greet` in a variable and call it.
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');

無名関数

#

ほとんどの関数(main()printElement()など)には名前を付けますが、名前のない関数を作成することもできます。これらの関数は、匿名関数ラムダ、またはクロージャと呼ばれます。

匿名関数は、次のような名前付き関数に似ています。

  • ゼロ個以上のパラメータ、コンマ区切り
  • 括弧内のオプションの型アノテーション。

次のコードブロックには、関数の本体が含まれています。

dart
([[Type] param1[, ...]]) {
  codeBlock;
}

次の例では、型指定されていないパラメータitemを持つ匿名関数を定義しています。匿名関数はそれをmap関数に渡します。リストの各アイテムに対して呼び出されるmap関数は、各文字列を大文字に変換します。次に、forEachに渡された匿名関数は、変換された各文字列をその長さとともに表示します。

dart
const list = ['apples', 'bananas', 'oranges'];

var uppercaseList = list.map((item) {
  return item.toUpperCase();
}).toList();
// Convert to list after mapping

for (var item in uppercaseList) {
  print('$item: ${item.length}');
}

実行をクリックしてコードを実行します。

void main() {
  const list = ['apples', 'bananas', 'oranges'];

  var uppercaseList = list.map((item) {
    return item.toUpperCase();
  }).toList();
  // Convert to list after mapping

  for (var item in uppercaseList) {
    print('$item: ${item.length}');
  }
}

関数が単一の式またはreturnステートメントのみを含む場合は、アロー表記を使用して短縮できます。次の行をDartPadに貼り付けて、機能的に同等であることを確認するために実行をクリックします。

dart
var uppercaseList = list.map((item) => item.toUpperCase()).toList();
uppercaseList.forEach((item) => print('$item: ${item.length}'));

レキシカルスコープ

#

Dartはコードのレイアウトに基づいて変数のスコープを決定します。この機能を持つプログラミング言語は、レキシカルスコープ言語と呼ばれます。変数がスコープ内にあるかどうかを確認するには、「外側の波括弧をたどる」ことができます。

例: 各スコープレベルに変数が配置された一連のネストされた関数

dart
bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

nestedFunction()メソッドは、トップレベルまで、すべてのレベルの変数を使用できます。

レキシカルクロージャ

#

関数がそのスコープの外側にある場合でも、そのレキシカルスコープ内の変数にアクセスできる関数オブジェクトは、クロージャと呼ばれます。

関数は、周囲のスコープで定義された変数にクロージャすることができます。次の例では、makeAdder()は変数addByをキャプチャします。返された関数はどこに行ってもaddByを記憶します。

dart
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

ティアオフ

#

関数、メソッド、または名前付きコンストラクタを括弧なしで参照すると、Dartはティアオフを作成します。これは、関数と同じパラメータを取り、それを呼び出すときに基になる関数を呼び出すクロージャです。コードで、クロージャが受け入れるのと同じパラメータを持つ名前付き関数を呼び出すクロージャが必要な場合は、ラムダで呼び出しをラップしないでください。ティアオフを使用してください。

dart
var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
gooddart
// Function tear-off
charCodes.forEach(print);

// Method tear-off
charCodes.forEach(buffer.write);
baddart
// Function lambda
charCodes.forEach((code) {
  print(code);
});

// Method lambda
charCodes.forEach((code) {
  buffer.write(code);
});

関数の等価性テスト

#

これは、トップレベル関数、静的メソッド、およびインスタンスメソッドを等価性についてテストする例です。

dart
void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  Function x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

戻り値

#

すべての関数は値を返します。戻り値が指定されていない場合、ステートメントreturn null;が関数本体に暗黙的に追加されます。

dart
foo() {}

assert(foo() == null);

関数で複数の値を返すには、値をレコードに集約します。

dart
(String, int) foo() {
  return ('something', 42);
}

ジェネレーター

#

値のシーケンスを遅延生成する必要がある場合は、ジェネレーター関数を検討してください。Dartには、2種類のジェネレーター関数が組み込まれています。

  • 同期ジェネレーター: Iterableオブジェクトを返します。
  • 非同期ジェネレーター: Streamオブジェクトを返します。

同期ジェネレーター関数を実装するには、関数本体をsync*とマークし、yieldステートメントを使用して値を配信します。

dart
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

非同期ジェネレーター関数を実装するには、関数本体をasync*とマークし、yieldステートメントを使用して値を配信します。

dart
Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

ジェネレーターが再帰的な場合は、yield*を使用してパフォーマンスを向上させることができます。

dart
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

外部関数

#

外部関数は、その本体が宣言とは別に実装されている関数です。次のように、externalキーワードを関数宣言の前に含めます。

dart
external void someFunc(int i);

外部関数の実装は、別のDartライブラリから、またはより一般的には別の言語から取得できます。相互運用コンテキストでは、externalは、外部関数または値の型情報を提供し、それらをDartで使用できるようにします。実装と使用はプラットフォームに大きく依存するため、たとえばCまたはJavaScriptの相互運用ドキュメントを確認して詳細を学んでください。

外部関数は、トップレベル関数、インスタンスメソッドゲッターまたはセッター、またはリダイレクトされないコンストラクタにすることができます。また、インスタンス変数externalにすることもできます。これは、外部ゲッター(および変数がfinalでない場合は外部セッター)と同等です。