前のページ

言語編

次のページ


関数(Functions)

関数は実行可能なアクションの抽象化である。Dartでは関数もオブジェクトでその型はFunctionである。即ちDartの関数は第1級関数(first-class function)である。従って後述のように、ある関数を別の関数にパラメタとして渡すことも可能である。またあるDartのクラスのインスタンスをあたかもそれが関数であったかのように呼び出すことも可能である。その詳細は「関数のエミュレーション (Emulating functions)の章を参照されたい。

関数には関数宣言(function declarations)、メソッド(methods)、ゲッタ(getters)、セッタ(setters)、コンストラクタ(constructors)、及び関数リテラル(function literals)がある。

関数宣言にはライブラリのトップ・レベル(即ちクラスのメンバでない)にある関数(例えばmain()でトップ・レベル関数という)、あるクラスに結び付けられた関数(static関数とかクラス関数という)、あるオブジェクトに結び付けられた関数(インスタンス・メソッドという)、および関数の内部に宣言されたローカル関数がある。関数の中にネストした関数を持つことも可能である。

JavaScriptと違って定義の為のfunctionといったキーワードは存在しない。

総ての関数はひとつのシグネチュアとひとつのボディを持つ。シグネチュアはその関数の仮パラメタたち(formal parameters)、及びその名前と戻りの型を記述する。ボディはその関数によって実行される文たち(statements)が入っているひとつのブロック文(block statement)である。形式 => e の形式の関数ボディは{return e;}の形式のボディと等価である。

ある関数の最後の文がreturn文でないときは、その関数ボディに対してはreturn null;という文が暗示的に付加される。

また関数宣言が戻り値の型を明示的に指定していないときは、その型はdynamic型として扱われる。ここにdynamicは未知(unknown)という意味の型である。

以下はある関数の実装例である:

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

関数宣言に型アノテーションを付すことは推奨されている(Effective Dart参照)ことだが、この関数は型を省略しても動作する:

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

単に一つだけの式からなる関数にはより簡素化した構文が使える。これは一行関数(one-line function)と呼ばれる最も簡単な関数宣言で:

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

=> exprという構文は{ return expr; }の短縮形である。=>表記はしばしば矢印構文(arrow syntax)と呼ばれる。

注意:式(文ではなくて)のみが矢印(=>)とセミコロン(;)の間に置かれる。例えば、if文はそこにおけないが、条件式は使える。

関数はふたつのタイプのパラメタたち:即ち必須(required)とオプショナル(optional)のパラメタたちを持てる。必須パラメタたちは最初にリストされ、以下に任意のオプショナルなパラメタたちが置かれる。名前付きオプショナル・パラメタたちは@requiredアノテーションでマークできる。

必須パラメタとオプショナルなパラメタは以下のように整理される:

パラメタの形式

必須パラメタ

オプショナルな位置的パラメタ

オプショナルな名前付きパラメタ

宣言時のパラメタ・リストの形式

パラメタ間をカンマで区切る

[]で囲ったカンマで区切られたパラメタたち

{}で囲ったカンマで区切られた名前付きのパラメタたち。名前付きのパラメタは名前:変数)の形式となる

宣言時のデフォルト値指定

不可

仮パラメタ=値で指定

名前=値で指定

呼び出し時の引数

指定された順にセット

指定された順にセットするが終わりのパラメタから連続してデフォルト値を使うときはそれらを省略可

名前=値で指定し、セット順は問わない。省略可

詳細は次節を参照のこと。

オプショナル・パラメタ(Optional parameters)

オプショナルなパラメタは位置的(positional)なものまたは名前付き(named)のいずれかがとなるが、双方ともはなれない。

オプショナルな名前付きパラメタ

ある関数を呼ぶ際に、paramName: valueを使って名前付きパラメタを指定できる。例えば:

enableFlags(bold: true, hidden: false);

関数を定義する際名前付きパラメタを指定するときはそれらを波括弧で囲んだ{param1, param2, …}を使う:

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

Flutterでのインスタンス生成式は複雑になり得るので、ウィジェット・コンストラクタはもっぱら名前付きパラメタを使う。そうするとインスタンス生成式が読みやすくなる。

単にFlutterだけでなく、どの環境のDartコード内でも名前付きパラメタを付すことができる。例えば:

const Scrollbar({Key key, @required Widget child})

Scrollbarが生成される際、このchild引数が欠けていると問題を報告する。

Requiredmetaパッケージの中で定義されている。 package:meta/meta.dartを直接インポートするか、またはFlutterpackage:flutter/material.dartのようなmetaをエクスポートする別のパッケージをインポートする。

オプショナルな位置的パラメタ (Optional positional parameters)

角括弧[]内に関数パラメタたちを包むとそれはオプショナルな位置的パラメタたちであることを示す:

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');

また以下はこの関数を3番目のパラメタ付きで呼んだ例である:

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

もう一つの例を示そう:

main() {
  // The following are all valid.
  connectToServer('secret');
  connectToServer('secret', '1.2.3.4');
  connectToServer('secret', '1.2.3.4', 9999);
}
connectToServer(String authKey, [ip = '127.0.0.1', port = 8080]) {
  print('aunthKey = $authKey, ip = $ip, port = $port');
}
/*
aunthKey = secret, ip = 127.0.0.1, port = 8080
aunthKey = secret, ip = 1.2.3.4, port = 8080
aunthKey = secret, ip = 1.2.3.4, port = 9999
*/

この場合は呼び出しにあたっては位置的パラメタには名前が付すことができず、名前付きでは指定できない。

この例では:

  • connectToServerという関数は3つの位置的パラメタが使われている

  • 呼び出すときはこの3つのパラメタをその順にセットする

  • 最初のauthKeyは必須パラメタだが、ipportはオプショナルでデフォルト値がセットされている

  • ipをデフォルト値以外の値にするときは2番目のパラメタとしてセットする

  • portをデフォルト値以外の値にしたいときは、ipの値もセットしなければならない



デフォルトのパラメタ値 (Default parameter values)

名前付き及び位置的パラメタたちともに対しデフォルト値を定義するのに=が使える。デフォルト値はコンパイル時常数でなければならない。デフォルト値が与えられていない場合は、そのデフォルト値はnullである。

以下は名前付きパラメタたちにデフォルト値を設定している例である:

/// 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);

廃止対象の注意:名前付きパラメタたちにデフォルト値をセットするのに=ではなくてコロン(:)が使われている可能性がある。その理由は当初は名前付きパラメタたちには:のみがサポートされていた為である。このサポートは廃止対象になる可能性があるので、デフォルト値の位置的パラメタたちには=を使用することを進める。

以下は位置的パラメタたちにデフォルト値を設定している例である:

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');

リストとマップもデフォルト値として渡すことができる。以下の例はdoStuff()という関数を定義しているが、listパラメタにはデフォルトのリストを、giftsパラメタにはデフォルトのマップを指定している:

void doStuff(
    {List<int> list = const [1, 2, 3],
      Map<String, String> gifts = const {
        'first': 'paper',
        'second': 'cotton',
        'third': 'leather'
      }}) {
  print('list:  $list');
  print('gifts: $gifts');
}



main()関数 (The main() function)

各アプリケーションはそのアプリの入口の開始点となるトップ・レベルのmain()関数を持っていなければならない。このmain()関数はvoidを返し、引数としてオプショナルなList<String>パラメタを持つ。

以下はウェブ・アプリのためのmain()関数の例である:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

注意:上のコードのなかの..構文はカスケードと呼ばれる。カスケードを使うと単一のオブジェクトのメンバたちに複数の操作を行わせることができる。

以下は引数を受け付けるコマンド行アプリのためのmain()関数の例である:

// このアプリを次のようなコマンドで開始させる: dart 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ライブラリを使うことが可能である。



1級クラスのオブジェクトとしての関数 (Functions as first-class objects)

ある関数を別の関数のパラメタとして渡すことができる。例えば:

void printElement(int element) {
  print(element);
}
var list = [1, 2, 3];
// printElementをパラメタとして渡す
list.forEach(printElement);

これはlistの各要素ごとにprintElementという関数を実行させている。

ある関数を次のようにある変数に代入することもできる:

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

この例は匿名関数(anonymous function)を使っている。詳細は「匿名関数」の節で示されている。



関数内関数 (Inner functions)

関数内関数はローカル関数(local function)とも呼ばれ、関数内に関数を置くことは可能である。次の例ではimportantify(重要性強調)という内部関数が定義されている:

String sayHello(String msg, String to) {
  String importantify(msg) => '!!! ${msg} !!!';
  return '${importantify(msg)} to ${to}';
}
main() {
  print(sayHello('Urgent', 'Bill'));
}
/*
!!! Urgent !!! to Bill
*/

関数内関数はそれが呼ばれる前に定義されていなければならない。

関数を再帰的に使うことも可能である。これは階乗計算やフィボナッチ関数の計算等でよく使われる。



匿名関数 (Anonymous functions)

殆どの関数はmain()あるいはprintElement()といった名前が付されている。しかし匿名関数あるいは時にはラムダ(lambda)、クロージャ(closure)あるいは関数リテラル(function literal)とも呼ばれる名前のない関数を作ることも可能である。ある匿名関数をある変数に代入し、例えばあるコレクションからそれを追加あるいは削除できるといったことが可能である。

匿名関数は非同期のイベントやストリームの処理ではコールバック関数として頻繁に使用される。例えばFuturethenメソッドのAPIを見るとonValueonErrorのコールバック関数が登録される。これらはライブラリ編の「非同期プログラミング」の章を参考にされたい。

匿名関数は名前付き関数と同じに見えるーゼロまたはそれ以上のパラメタがカンマで区切られており、オプショナルな型アノテーションがあり、括弧で囲まれている。

関数のボディは波括弧で囲まれたコード・ブロックが含まれる:

([[Type] param1[, …]]) {
codeBlock;
};

以下の例は型付けされていないパラメタのitemを含んだ匿名関数を定義している。この関数はlistのなかの各itemごとに呼び出され、指定されたインデックスにある文字列の値をプリントする。

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}

DartPadで確認されたい:

この例のようにその関数がただ一つの文だけのときは矢印構文が使える。以下の行を上のコードに追加してDartPadで同じ結果が得られることを確認されたい。

list.forEach(
(item) => print('${list.indexOf(item)}: $item'));

次の例では匿名関数を変数として扱っている:

main() {
  var balance = 0;
var deposit = (amount) { balance += amount; };  // 預金1:関数リテラル
//  deposit(amount) { balance += amount; }          // 預金2:関数定義
var withdraw = (amount) { balance -= amount; }; // 引き出し1:関数リテラル
//  withdraw(amount){balance -= amount; }           // 引き出し2:関数定義
  deposit(1000);  // 預金呼び出し
  withdraw(100);  // 引き出し呼び出し
  print(balance);
}

関数を使っても関数リテラルを使っても呼び出しは同じで、また結果も同じである。関数リテラルは名前は不要である。その代わりFunction型のオブジェクトとしてdepositあるいはwithdrawという変数に代入されている。従って関数リテラルのオブジェクトは実行時に生成される。関数及び関数リテラルともにオブジェクトであるから別の関数の引数に使ったり、また戻しの値として使うことが可能になる。

関数式を()で括ったものは関数識別子と同じように扱える。

以下の例では2つの値の平均値を求める関数リテラルに108という2つの値を与えて実行し、その結果をプリントしている。つまりprintというメソッドの中の式として関数リテラルが組み入れられている:

/* 平均値 */
main() {
  print(((var x, y){return (x + y)/2;})(10, 8));
}
/*
9.0
*/

printの行は単行関数式を使って次のようにも書ける:

print(((x, y)=>(x + y)/2)(10, 8));

関数のパラメタに関数リテラルとデフォルト値を含むことができる。次の例ではconsoleOutは結果の文字列を表示する為のメソッド(ラッパー)・オブジェクトだが、これは呼び出し側で実体化する。またtoFahrenheitはどちらに変換するかを指定するが、デフォルトでは摂氏から華氏への変換が実行される。呼び出し側でこれを変更するときはtoFahrenheit: falseという具合に名前つきで指定する:

/* 摂氏と華氏の変換 */
tempUnitConvert(var temp, consoleOut, {bool toFahrenheit = true}) {
  var tempC, tempF;
  if (toFahrenheit){
    tempC = temp; tempF = tempC * 9 / 5 + 32;
  } else {
    tempF = temp; tempC = (tempF - 32) * 5 / 9;
  }
  String result = 'Celsius: $tempC,  Fahrenheit: $tempF';
  consoleOut(result);
}
main(){
  tempUnitConvert(32, (str){print(str);}, toFahrenheit: false);
  tempUnitConvert(32, (str){print('--- $str ---');});
}
/*
Celsius: 0,  Fahrenheit: 32
--- Celsius: 32,  Fahrenheit: 89.6 ---
*/

関数及び関数リテラルは再帰可能であるが、処理速度は一般に遅くなる。次のコードは関数の再帰可能性を示す為に良く使われるパタンだが、最初に呼ばれたときのnという変数とその関数の中で呼ばれた次の関数オブジェクトのnとは別のものであることを使っており、ややトリッキーである。

/* 階乗計算(関数) */
factorial(n) {
  if (n <= 1) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}
main() {
  print(factorial(5));
}
/*
120
*/



/* 階乗計算(関数リテラル) */
var factorial;
main() {
  factorial = (n) {
    if (n <= 1) {
      return 1;
    } else {
      return n * factorial(n - 1);
    }
  };
  print(factorial(5));
}
/*
120
*/

もうひとつ良く引用されるサンプルはFibonacci数列である。これはnが増えると計算時間が増えるので、時間がかかる処理のシミュレーションによく使われる:

main() {
  print(new Fibonacci().fib(10)); // 89
}
class Fibonacci {
  int fib( int value ) {
    if( value == 0 || value == 1 ) {
      return 1;
    }
    return fib( value - 1 ) + fib( value - 2 );
  }
}



静的スコープ (Lexical scope)

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()がどのようにトップ・レベルに至る各レベル(つまりnestedFunction内→myFunction内→main内→トップ・レベル)からの変数が使えるかを確認されたい。



静的クロージャ (Lexical closures)

クロージャは、その関数がそのオリジナルのスコープの外で使われていたとしても、その静的スコープ内の変数にアクセスできる関数オブジェクトのことを言う。

関数はそれを囲んでいるスコープ内で定義された変数たちを包含できる。以下の例では、makeAdder()は変数addByを捕まえている。戻された関数がどこに戻されようと、それは数字addByを覚えている。

/// [addBy]をこの関数の引数に加算する関数を返す
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}
void main() {
  // 2を加える関数を作る
  var add2 = makeAdder(2);
  // 4を加える関数を作る
  var add4 = makeAdder(4);
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}



関数の対等性のテスト (Testing functions for equality)

以下はトップ・レベルの関数、staticメソッド、及びインスタンス・メソッドが同じかどうかをテストしている例である:

void foo() {} // トップ・レベルの関数
class A {
  static void bar() {} // staticメソッド
  void baz() {} // インスタンス・メソッド
}
void main() {
  var x;
  // トップ・レベルの関数同士を比較する
  x = foo;
  assert(foo == x);
  // staticメソッドたちを比較
  x = A.bar;
  assert(A.bar == x);
  // インスタンス・メソッドたちを比較
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;
  // これらのクロージャたちは同じインスタンス(#2)を参照しているので、
  // これらはイコールである
  assert(y.baz == x);
  // これらのクロージャたちは異なったインスタンスを参照しているので、
  // これらはイコールではない
  assert(v.baz != w.baz);
}



戻り値 (Return values)

総ての関数は値を返す。もし返し値が指定されていないときは文return null;が暗示的にその関数ボディに付加される。

foo() {}
assert(foo() == null);

その関数の型が指定されていないときはdynamicが指定されていると考えても良い。nulldynamic型なので、上のコードは

dynamic foo() {}
assert(foo() == null);

と書いても同じ効果となる。





前のページ

次のページ