243
209

More than 1 year has passed since last update.

Dartの基本文法を振り返る

Last updated at Posted at 2019-06-10

Dart

DartはGoogleによって開発されたプログラミング言語です。
もともと、JavaScriptを置き換える言語を目指していましたが、Googleがとりあえず諦めてしまったようなので、今はAltJSの選択肢の一つになっています。

なぜ、今Dartなのかというと

  • 最近Dartを使っているFlutterが流行っているから
    • FlutterでWebアプリが作れる
    • FlutterのWindows版が正式版として使える
    • Flutterの他のPC版(macOS, Linux)がbetaで動くようになってきてる
  • 個人的にDartが好きだから

Dartの言語仕様

Dartはコンパクトな言語仕様らしく仕様書は目次などを含め150ページほどです。(C++17のドラフトは1500ページ弱あります。)

基本形はJavaで、それにJavaScriptの考え方の一部を取り入れた感じです。
影響を受けた言語(from Wikipedia)ではJavaScript, Java, Smalltalk, Erlang, Strongtalk, C#となっていました。
このJava風の言語仕様はすでにWebでJavaScriptを書いてきた人向けというよりは、JavaやSwiftでアプリを書いてたりC++を使って何か書いていたりみたいな人向けなのかもしれません。

DartはネイティブコンパイルやJavaScriptへのトランスパイルができ、DartVMというVM上で動かすこともできます。
コンパイルにはdart compileというコマンドを使います。
このコマンドはexeaot-snapshotjit-snapshotkerneljsをサブコマンドとして取ります。
ネイティブコンパイルする場合はexeを使用します.このサブコマンドではプラットフォームに依存した実行ファイルを作成します。これには記述したDartのプログラムとDartのランタイムが含まれます。
aot-snapshotはDartのプログラムをプラットフォームに依存するバイナリを作成しますがDartのランタイムは含まれません。これで作成されたプログラムはdartaotruntimeコマンドを使い実行します。
jit-snapshotはDartプログラムを中間表現にコンパイルします。実行にはdart runコマンドを使用します。これで作成されたプログラムはプラットフォームに依存します。
kernelは中間表現 (カーネルAST) にコンパイルします。これはプラットフォームに依存しない形式です。実行にはdart runコマンドを使用します。
jsはJavaScript`にコンパイルします。

dart compile exe bin/main.dart
dart compile aot-snapshot bin/main.dart
dart compile jit-snapshot bin/main.dart
dart compile kernel bin/main.dart
dart compile js bin/main.dart

環境

Dartの開発にはいくつかの選択肢がありますが、IntelliJ IDEAを使用するのが一番簡単だと思います。

ここからの流れ

  • 変数
  • 関数
  • クラス
  • パッケージ
  • 非同期

の順で説明していきたいと思います。

また、null safetyになってから結構経つので、null safetyであること前提で記述している箇所もありますので、null safety以前のDartをお使いの場合は気を付けてください。

基本型

  • 数値型はintdouble(それ以外にも数値を扱う型はあります)
  • 文字列はString
  • リストにはList<T>
  • 連想配列にはMap<K, V>

型の表記

Dartの型の表記にはdynamicvarを使うこともできます。
dynamicは動的型でList<dynamic>などのように書きたい時にも使えます。
varは型推論を行い代入時の型になります。なので、再代入時に別の型の値を入れるとエラーになります。

動的型を乱用することは良いとは思いませんが、動的型を使わないと記述不可能な場面は出てくると思いますので、ここぞという時に使うと良いと思います。

型のTips

Dartの型の多くはObject型からの派生です。つまり、intdoubleStringも全てObjectから派生しています。
しかし、例外としてnullの型であるNull型はObjectから派生していません。
そのため、次のような実行結果になります。

null is Object; // false

int? i1;
i1 is Object; // false

i1 = 0;
i1 is Object; // true

int i2 = 0;
i2 is Object; // true

また、Dartでは暗黙の型変換ができません。例えば一般的には普通にできるint => doubleの変換はそのまま代入では不可能ということです。なのでtoInt()のようなメンバ関数が用意されています。

null safety

Dart 2.12からnullを許容する型と許容しない型を明示することができるようになりました。
KotlinやSwiftにも同等の機能があります。
この機能によってモダンな感じでコードを書くことができます。

int? // nullable
int  // not null

上記の例のように型名?のような型がnullになりうる型で、?が付かない型がnullを許容しない型です。
ここからはnullになりうる型をnull許容型、そうでない方を非null型と呼びます。 (勝手にそう呼ぶだけです)

null許容型はnullが入っているかもしれない型なので、そのまま値を使おうとするとエラーになります。
例えば以下のような場合はエラーになります。

int? a = null;

a.toString(); // aはnullかもしれない

そのため、null許容型を非null型にしなければなりません。

int? a = null;

if (a != null) {
  a.toString(); // aはnullかもしれない
}

この場合a.toString()をする前につまりnull許容型の値を使う前にnullチェックをしています。
これによりif文の中ではanullでないと言えるので普通にa.toString()を使うことができます。

また、これは昔からある機能ですが、?.??によって、値がnullnullでないかで処理を分けることができます。

int? a = null;

a?.toString(); // a == nullであればnull、a != nullであればa.toString()の値になる
a ?? 0; // a == nullであれば0、a != nullであればaの値になる

変数

Dartの変数は型をJavaの書式とほぼ同じです。

int number = 0;
String str = 'Dart';
var one = 1;

finalとconst

Dartは型の修飾子としてfinalconstが存在しています。
finalはJavaのfinalと同じ意味です。つまり再代入不可であることを示します。
constはコンパイル時定数。つまり、「再代入不可 + そのメモリ領域の書き換え不可」を表します。

final int zero = 0;
final one = 1; // 型推論もできる

const int two = 2;
const three = 3; // こっちでも型推論できる

不可視な識別子

Dartはそのファイル内など見える範囲を制限することができます。
ある識別子(変数とか関数とか)を他のソースで見えないようにするには_で名前を始めます。

int visible = 0;
int _invisible = 0;

なので、Dartにはpublicとかprotectedとかprivateとかの予約語がありません。

関数

Dartはもちろん関数があります。
Javaとは違い関数はクラスに属する必要がありません。

JavaやC++, C#などの言語からDartを始めた人が割と戸惑う点としてDartは関数のオーバーロードをサポートしていません。
なので、全ての関数は別の名前がつけられている必要があります。

関数は次のように書きます。

int func(int a, int b) {
  return a + b;
}

完全にC系の言語の書き方です。
Dartではさらに返り値の型を省略することもできます。

func(int a, int b) {
  return a + b;
}

また、引数の型も省略できます。

func(a, int b) {
  return a + b;
}

sync*

Dartは関数の後ろにsync*をつけることでPythonで言うジェネレータを定義することができます。
ここで出てくるsyncというのは同期 (synchronous) という意味で、後に説明する非同期に対して同期と言っています。

// [0, max)の整数列を返すiota関数
Iterable<int> iota(int max) sync* {
  for(int i = 0; i < max; ++i) {
    yield i;
  }
}

// main
//  実行結果
//   0
//   1
//   2
//   3
//   4
void main() {
  for(final n in iota(5)) {
    print(n);
  }
}

このコード例のyieldは「生む」や「譲る」という意味があります。
yieldを行うと一つ値を「生む」処理を行います。これをfor文で実行した場合は次のような動作になります。

  1. yieldが来た時点で呼び出し元のに実行権を「譲る」処理を行います。
  2. 呼び出し元のforで1回ループが周ります。
  3. ループが回った後にもう一度yieldの続きからジェネレータの処理が継続されます。

この処理がジェネレータ関数を抜けるまで繰り返されます。

メソッド

Dartはクラスに属する関数を作ることができます。
一般的には非メンバ関数と同じ書き方ですが、暗黙的に自インスタンスへの参照としてthisが渡されます。

コンストラクタ

クラスを構築する特殊な関数としてコンストラクタがあります。
コンストラクタにはいくつかの書き方が存在しています。

普通のコンストラクタ

一般的なクラスベースのオブジェクト指向言語と同じくDartの普通のコンストラクタはクラス名と同一の名前をもち返り値の型を書きません。

class Klass {
  Klass();
}

名前付きコンストラクタ

Dartは関数のオーバーロードができませんが、この制約はコンストラクタにも付きます。
なので普通のコンストラクタを作ってしまうと、別の引数をとる普通のコンストラクタを作ることができなくなります。
そこで、Dartにはコンストラクタを名前付きで作ることができます。

class Klass {
  Klass.fromString(String f);
}

もうお分かりだと思いますが、名前付きコンストラクタも同名では作ることはできません。

ファクトリ

factoryをコンストラクタに指定すると、そのコンストラクタをファクトリとして指定することができます。
ファクトリは一般的なコンストラクタとは違いインスタンスを作成し返します。

インスタンス生成に引数を作るなどの前処理が必要であったり、インスタンス生成後にしかできない処理をする際に便利です。

class Klass {
  Klass._private(); // 非公開な名前付きコンストラクタ
  factory Klass() {
    var instance = Klass._private(); // newは省略できる
    return instance;
  }
}

定数コンストラクタ

定数オブジェクトを作成することができるコンストラクタです。
メンバ変数は全てfinalで修飾し、コンストラクタ名の前にconstをつけます。

class Klass {
  final String name;
  const Klass(this.name); // this.nameのようにするとnameという引数で受けそのままthis.nameに代入できる
}

特殊な引数

Dartは名前付き引数とオプションの引数をサポートしています。

名前付き引数

名前付き引数は引数の名前を指定して値を渡す引数です。
名前付き引数を使うことで、次のような利点があります。

  • 仮引数名が適切であれば読みやすいコードになる
  • 引数の順番にこだわる必要がない

名前付き引数は引数を{}で括ります。

func({first, last});

// 呼び方
func(first: 'f'); // firstだけ指定
func(last: 'l'); // lastだけ指定
func(first: 'f', last: 'l'); // firstとlastを指定

このfirstlastが名前付き引数となります。

名前付き引数にした引数は引数が指定されないということもできてしまいます。
指定されなかった場合はデフォルトでnullになります。
しかし、名前付き引数にはしたいけど、絶対引数は渡してほしいということはあると思います。
この両方を満たすために使われるのが@requiredです。

func({first, @required last});

@requiredmetaというライブラリに含まれているようで標準ではありませんが、Flutterでは使えるので知っておくと便利だと思います。

Dart 2.12からはnull safetyが標準となり、引数を渡さないとnullになるという仕様と@requiredがバッティングしてしまいます。
Dart 2.12からは@requiredではなくrequiredというキーワードを使い必ず渡す必要があることを明示できます。

func({first, required last});

オプションの引数

オプションの引数は渡さなくても良い引数のことです。

func([first, last]);

デフォルトの引数

名前付き引数やオプションの引数で渡されなかった引数にはnullが入ります。
これをnull以外の値にしたいときにデフォルトの引数を使用します。

named({first, last='l'});
opt([first, last='l']);

この例では両方ともlastに引数が指定されなかった場合に'l'が使用されます。

クラス

DartはJavaScriptとは異なりクラスベースのオブジェクト指向をサポートしています。

クラスの書き方はJavaに似ています。

class Klass {
  String _name;

  String getName() {
    return _name;
  }
}

これはDartのコードですがJavaのコードにも見えます。
前述の通りDartはアクセス修飾を変数の名前で示すため、privatepublicなどの単語を書きません。
この例では_nameがprivateのようなもので、getNameがpublicとなります。

プロパティ

最近の言語はプロパティという仕組みを持っています。
プロパティはゲッターとセッターを扱いやすくしたものです。

class Klass {
  String _name; // 外部からは取得だけ許可したい

  String get name => _name;
}

この例のnameがプロパティでゲッターです。

セッターは次のように作ります。

class Klass {
  String _name;

  String get name => _name;
  set name(String v) {
    _name = v;
  }
}

パッケージ

Dartにはパッケージという概念があります。
パッケージはライブラリのようなものでいくつかの機能などをひとまとめにしたものです。

パッケージの作り方はここでは省略し、使い方を紹介します。

import

Dartはパッケージをimportという単語を使用し読み込みます。

import 'dart:math';

この例ではDartの標準ライブラリのなかのmathを読み込んでいます。

as

単にimportをしただけでは中の識別子が名前空間もなく使えるようになります。
しかし、名前の衝突を避けるために名前空間のようなものを指定したいことがあります。
ここで使うのがasです。

import 'dart:math' as m;

とすることでdart:math内の識別子がm.???の形式で使えるようになります。

show

読み込んだ識別子の一部だけ使えるようにするためにはshowを使います。

pub

pubはDartのパッケージマネージャです。
Dartで外部パッケージを使用する場合に使われます。
pubspec.yamlというファイルにパッケージの情報を書きdart pub getコマンドを実行することでパッケージがインストールできます。
または,dart pub add <パッケージ名>とも書けます。このように書いた場合はpubspec.yamlにも同時に書き込まれます。
Flutterの場合はflutter pub getコマンドです。
パッケージの更新を行う場合はgetupgradeにします。

ここらへんはIntelliJ系のIDEであればFlutterプラグインなどでボタン一つで行えるのであまり気にしなくても良いかもしれません。

非同期

Dartは非同期処理をサポートしています。
非同期ではFuture<T>というクラスを使用します。Future<T>は非同期処理を管理(?)するためのクラスで終了時に呼ばれる関数などを指定することができます。
Future<T>Tを受け取るという意味の型としてFutureOr<T>もあります。Futuredart:coreに含まれるためimportは不要です。FutureOrdart:asyncに含まれています。

await

awaitは非同期な処理の終了を待機することを示す単語です。
awaitを使うためには関数をasyncに指定する必要があります。asyncに指定した関数は返り値の型がFutureになります。

Future<void> asyncFunc() async {
  // do something
}

// 使う側
Future<void> asyncCaller() async {
  return await asyncFunc();
}

asyncasync*

Dartはasyncの代わりにasync*と書くことで返り値をStream<T>にすることができます。
asyncasync*の関係は、普通の関数とsync*の関係と同じです。
async*を使うことでyieldを使うことができるようになります。

また、Stream<T>を手軽に扱うためにDartにはawait forという機能があります。
これはStream<T>forと同様の構文で回すための機能です。

Stream<int> getStream() {...}

Future<void> main() async {
  await for (final n in getStream()) {
    // do something
  }
}

終わりに

以上、Dartの基本的な機能をさらっと紹介しました。

Dartを手軽に試す方法としてDartPadがあるので使ってみてください。

243
209
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
243
209