Introduction to Dart を見ておく (2)

# はじめに

https://qiita.com/grhg/private/9d2a9b30942724c77a7b

の続きで、Introduction to Dart をやっていきます。

https://dart.dev/language

# Introduction to Dart

続きをやっていきます。
[DartPad](https://dartpad.dev) を使えば、Web上で実行することができます。

## Enums

### シンプルな定義

```dart
enum PlanetType { terrestrial, gas, ice }

void main() {
  const planet = PlanetType.terrestrial;
  print(planet);
}
```

実行結果
```
PlanetType.terrestrial
```

### 拡張した使い方

以下のように Enum にプロパティやメソッドを持たせることもできる。

```dart
enum PlanetType { terrestrial, gas, ice }

/// 太陽系惑星野列挙隊
enum Planet {
  mercury(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
  venus(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
  // ···
  uranus(planetType: PlanetType.ice, moons: 27, hasRings: true),
  neptune(planetType: PlanetType.ice, moons: 14, hasRings: true);

  /// コンストラクタ
  const Planet(
      {required this.planetType, required this.moons, required this.hasRings});

  /// 変数はfinalで定義する
  final PlanetType planetType;
  final int moons;
  final bool hasRings;

  /// getter や メソッドも定義可能
  bool get isGiant =>
      planetType == PlanetType.gas || planetType == PlanetType.ice;
}


void main() {
  const planet = Planet.uranus;
  print(planet);
  print(planet.planetType);
  print(planet.moons);
  print(planet.hasRings);
  print(planet.isGiant);
}
```


## Inheritance

継承は単一のみ。(複数の親クラスを持つことはできない)

```dart
class Spacecraft {
  String name;
  DateTime? launchDate;

  int? get launchYear => launchDate?.year;

  Spacecraft(this.name, this.launchDate) {
  }

  Spacecraft.unlaunched(String name) : this(name, null);

  void describe() {
    print('Spacecraft: $name');

    var launchDate = this.launchDate;
    if (launchDate != null) {
      int years = DateTime.now().difference(launchDate).inDays ~/ 365;
      print('Launched: $launchYear ($years years ago)');
    } else {
      print('Unlaunched');
    }
  }
}

//
// ↑は Class の説明で作成したもの
//


class Orbiter extends Spacecraft {
  double altitude;

  Orbiter(super.name, DateTime super.launchDate, this.altitude);
  
  // アノテーションで継承する
  @override
  void describe() {
    // 継承元は super で呼び出す
    super.describe();
    print('Altitude: $altitude');
  }
}

void main() {
  var voyager = Orbiter('Voyager I', DateTime(1977, 9, 5), 1234.56);
  voyager.describe();
}
```

出力結果

```bash
Spacecraft: Voyager I
Launched: 1977 (47 years ago)
Altitude: 1234.56
```

## Mixin

多重継承の代わり(でもないが)に機能の再利用には Mixin というのを使う。


```dart
// ...


mixin Piloted {
  int astronauts = 1;

  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

// with で 利用する Mixinを指定(カンマ区切りで複数指定可能)
class Orbiter extends Spacecraft with Piloted {
  double altitude;

  Orbiter(super.name, DateTime super.launchDate, this.altitude);
  
  @override
  void describe() {
    super.describe();
    print('Altitude: $altitude');
    // Mixinのメソッドを呼び出す
    describeCrew();
  }
}

void main() {
  var voyager = Orbiter('Voyager I', DateTime(1977, 9, 5), 1234.56);
  voyager.describe();
}
```

出力結果

```bash
Spacecraft: Voyager I
Launched: 1977 (47 years ago)
Altitude: 1234.56
Number of astronauts: 1
```

## Interfaces and abstract classes

全てのクラスは Interface としても扱える

```dart
// extends の代わりに implements にするだけ
class MockSpaceship implements Spacecraft {
  // ···
}
```

abstruct で抽象クラスを定義できる。
(interface として使うならこちらの方が良さそう)

```dart
abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}
```

## Async

非同期は async の指定で行います。

```dart
const oneSecond = Duration(seconds: 1);
// ···
Future<void> printWithDelay(String message) async {
  await Future.delayed(oneSecond);
  print(message);
}

// async を使わず以下のように書くこともできる
// Future<void> printWithDelay(String message) {
//   return Future.delayed(oneSecond).then((_) {
//     print(message);
//   });
// }

void main() {
  printWithDelay("finished"); 
  print("main end");
}
```

出力結果

```bash
main end
finished
```

### async*

async の代わりに async* を使うことで、戻り値をストリームにすることができます。

```dart
const oneSecond = Duration(seconds: 1);
// ···
Stream<String> numbers(int max) async* {
  for (int i = 0; i < max; i++) {
    await Future.delayed(oneSecond);
    // yield でストリームに追加(return のようなもの)
    yield 'count ${i + 1} !';
  }
}

void main() {
  // listen で Stream の値を順次受け取る (forEach的なもの)
  numbers(5).listen((val) {
    print(val);
  });
}
```

出力結果(1秒おきに一行ずつ出力されます)

```bash
count 1 !
count 2 !
count 3 !
count 4 !
count 5 !
```

これは便利ですね。

## Exceptions

お馴染みの例外。throw で例外を投げて、catch で捕捉します。

```dart
void checkAstronauts(int astronauts) {
  // 3で割り切れる時にエラーにする
  if (astronauts % 3 == 0) { 
    throw StateError('Invalid astronauts.');
  }
}


void main() {
  for(int i = 0; i < 10; i++) {
    try {
      checkAstronauts(i);
    } on StateError catch (e) {
      // StateError が発生したときはキャッチ
      print('Exception occurred: $e');
    } finally {
      // finally は常に実行
      print('go next: $i');
    }
  }
}
```

出力結果

```bash
Exception occurred: Bad state: Invalid astronauts.
go next: 0
go next: 1
go next: 2
Exception occurred: Bad state: Invalid astronauts.
go next: 3
go next: 4
go next: 5
Exception occurred: Bad state: Invalid astronauts.
go next: 6
go next: 7
go next: 8
Exception occurred: Bad state: Invalid astronauts.
go next: 9
```

## Important concepts

Dart を学ぶ上で重要な概念。
翻訳を置いておきますが、必要に応じで[原文](https://dart.dev/language#important-concepts)を参照してください。

- 変数に入れることができるものはすべてオブジェクトであり、すべてのオブジェクトはクラスのインスタンスです。数字、関数、そしてnullもオブジェクトです。null（音響的なnull安全性を有効にした場合を除く）以外のすべてのオブジェクトはObjectクラスから継承されます。

:::note
バージョンノート: Null安全性はDart 2.12で導入されました。null安全性を使用するには、少なくとも2.12の言語バージョンが必要です。
:::

- Dartは強い型付けを持っていますが、型注釈はオプションです。Dartは型を推論できます。例えば、var number = 101では、numberはint型であると推論されます。
- Null安全性を有効にすると、変数にnullを含めることはできなくなります。nullを許可する場合は、型の末尾に質問符（?）を付けます。例えば、int? 型の変数は整数かもしれないし、nullかもしれません。式が決してnullにならないとわかっている場合でも、Dartがそう判断しない場合は、! を追加してnullではないことをアサートできます（もしnullであれば例外がスローされます）。例: int x = nullableButNotNullInt!

- どんな型でも許可することを明示的に示したい場合、Object?（null安全性を有効にした場合）、Object、またはランタイムまで型チェックを延期する必要がある場合は特別な型dynamicを使用します。

- Dartはジェネリック型をサポートしています。例えば、List<int>（整数のリスト）やList<Object>（任意の型のオブジェクトのリスト）などです。

- Dartはトップレベルの関数（例えば、main()）をサポートしており、クラスやオブジェクトに関連付けられた関数（静的およびインスタンスメソッド）もサポートしています。また、関数内に関数を作成することもできます（ネストした関数またはローカル関数）。

- 同様に、Dartはトップレベルの変数をサポートしており、クラスやオブジェクトに関連付けられた変数（静的およびインスタンス変数）もサポートしています。インスタンス変数はフィールドやプロパティとも呼ばれます。

- Javaとは異なり、Dartにはpublic、protected、およびprivateキーワードがありません。識別子がアンダースコア（_）で始まる場合、その識別子はライブラリ内でプライベートです。詳細については、Libraries and importsを参照してください。

- 識別子は文字またはアンダースコア（_）で始まり、その後にこれらの文字や数字の任意の組み合わせを続けることができます。

- Dartには、ランタイム値を持つ式（expressions）と、値を持たない文（statements）の両方があります。例えば、条件式 condition ? expr1 : expr2 はexpr1またはexpr2の値を持ちます。これに対して、if-else文は値を持ちません。文は通常、1つ以上の式を含みますが、式は直接文を含むことはできません。

- Dartツールは2種類の問題を報告できます：警告とエラー。警告は、コードが動作しない可能性があることを示すものであり、プログラムの実行を妨げるものではありません。エラーはコンパイル時またはランタイム時に発生します。コンパイル時エラーはコードの実行を完全に防ぎ、ランタイムエラーはコードの実行中に例外を引き起こします。


# 終わり

Dart の基本文法を見てきました。
後発の言語だけあって、モダンな表現が結構できるので、TypeScript や Kotlin での実装に慣れてるとかなり書きやすそうです。
あまり違和感のある部分はなかったので、残りは書きながら覚えていこうと思います。