Java, PHP習得者がDartを学ぶ 2

スマホアプリ開発を夢見てFlutter/Dartの勉強を真剣にはじめてみたバックエンドエンジニア歴十数年の筆者が躓いた点やプログラミングに関してあらためて理解を深めたこと、などを記した備忘録です。

Dartの関数

Dartの関数は、第一級関数である

まずは意味を正しくおさえておきます。

計算機科学において、第一級関数(だいいっきゅうかんすう、英: first-class function、ファーストクラスファンクション)[1]とは、関数を第一級オブジェクトとして扱うことのできるプログラミング言語の性質、またはそのような関数のことである。その場合その関数は、型のある言語では function type(en:Function type)などと呼ばれる型を持ち、またその値は関数オブジェクトなどになる。具体的にはプログラムの実行時に生成され、データ構造に含めることができ、他の関数の引数として渡したり、戻り値として返したりすることのできる関数をいう。

第一級関数」(2020年10月2日 (金) 16:40 UTCの版) 『ウィキペディア日本語版』

Javascriptの関数も第一級関数であり、関数を他の関数の引数とするいわゆるコールバック関数などよく使いますが、Dartの関数も同じようにオブジェクトとして扱えるということですね。具体的には以下のようなことができます。

  • 関数を変数に代入できる
  • 関数を他の関数の引数にできる(コールバック関数)
  • 関数の戻り値に関数を指定できる

またDartの場合、関数オブジェクトとしてFunction型が用意されています

以上をふまえて、以降はサンプルコードを実行しながらの覚書です。

基本的な関数の書式

```
<戻り値の型> <関数名>(<引数の型> <引数名>, …) {
     … 処理 …
     return <戻り値>;    
}
```

関数定義例

// 定義に沿った関数例
String helloWorld1(String world) {
    return 'Hello $world';
}

// 戻り値と引数の型定義を省略できる
// 省略した場合の型はdynamicとして扱われる
helloWorld2(world) {
    return 'Hello $world';
}

// 戻り値がない場合の関数定義
void helloWorld3() {
    print('Hello');
}

// voidを省略した場合には、戻り値の型を明示的に指定していない場合(dynamic型)と同様に扱われる。
// 実際の戻り値はnullとなる。
helloWorld3() {
    print('Hello');
}

「dynamic」:どんな値でも代入できる型

アロー演算子

return文のみの関数をアロー演算子(=>)を使ってより短く記述することができる。

// 中かっことreturnを省略し、シグニチャと本体とを「=>」でつなぐ
String helloWorld4(String world) => 'Hello $world';

// void関数の場合、中かっこを省略し、シグニチャと本体とを「=>」でつなぐ
void test () => print("OK");

任意引数

[ ]の中に記述した場合は、省略可能な引数になる。
val2=0のように初期値を設定することもできる。指定しない場合にはnullが設定される。
※任意引数は必須引数の前に指定することはできない。

int calc(int val1, [int val2 = 0]) {
    // 処理
}
void main(){
    print(calc(10, 20));
    print(calc(10)); // 第2引数省略
}

名前付き引数

引数の役割を明確にするために引数に名前を付けることができる。名前付き引数は{ }内に定義する。
名前付き引数は省略可能なため、必要に応じて初期値を設定する。

int calc1({ int val1 = 0, int val2 = 0 }) {
    return val1 + val2;
}
int calc2(int val1, { int val2 = 0 }) {
    return val1 + val2;
}
void main() {
    print(calc1(val1:3, val2:5));
    print(calc1(val2:5, val1:3)); // 指定する引数の順番は関係ない
    print(calc2(3, val2:5));      // オプションとなる引数を名前付き引数で指定
}

名前付き引数は、required をつけることで必須であることを示すことができる。

int calc3({ required int val1, int val2 = 0 }) { // requiredまたは、初期値を設定しないとエラー
    return val1 + val2;
}
void main() {
    print(calc3(val1:3));
}

無名関数

関数名のない関数は、Dartでは次のように定義する。

(<引数の型> <引数名>, …) {
     … 処理 …
     return <戻り値>;    
}

無名関数の使用例。Dartは関数もオブジェクトの1つのため、関数を変数に代入できる。

var greeting = (name) {
    return 'Hello, ${name}!';
};
print(greeting('Dart'));

return文のみの無名関数は、アロー演算子(=>)を使って記述することができる。

var greeting = (name) => 'Hello, ${name}!';
print(greeting('Dart'));

第一級オブジェクトとしての関数

関数を他の関数の引数に指定(コールバック関数)

前述のとおり、関数はFunction型のオブジェクトなので、引数がコールバック関数の場合、Function型で定義する。

void itemPrice(String item, Function func) {
    Map<String, int> priceMap = {'りんご':150, 'みかん':80, 'キウイ':120};
    int? price = priceMap[item];
    func(item, price);
}
void pricePrint(String item, int price) {
    print('$itemの値段は$price円です。');
}
void main() {
    itemPrice('キウイ', pricePrint); // キウイの値段は120円です。
}

無名関数で記述した場合

void itemPrice(String item, Function func) {
    Map<String, int> priceMap = {'りんご':150, 'みかん':80, 'キウイ':120};
    int? price = priceMap[item];
    func(item, price);
}
void main() {
    itemPrice('みかん', (String item, int price) => print('$itemの値段は$price円です。')); // みかんの値段は80円です。
}

コールバック関数を指定する例(forEach)

forEach()は、List等のコレクション要素に対して繰り返し処理を行う関数でコールバック関数を引数に取る。

void printElement(String s) {
    print(s);
}
void main() {
    List<String> list = ['a', 'b', 'c'];
    list.forEach(printElement);
     // a
     // b
     // c
}

無名関数で記述した場合

List<String> list = ['a', 'b', 'c'];
list.forEach((String s) => print(s));

関数の戻り値に関数を指定

戻り値が関数の場合は、戻り値の型をFunction型で定義する。

Function hello (greeting) {
    return (name) {
        print('$greeting、$name!');
    };
}

void main() {
    hello('Hello')('Dart'); // Hello、Dart!
}