概要
現在弊社でリリースしている iOS アプリの Android バージョン開発に向けて、
Flutter が候補として挙がったので、 Dart について調べました。
どんな言語なのか、どんな書き方ができるのかなど、
1〜2日で調べた内容を共有します。
自己紹介
株式会社ITMONOでCTOをしています。
自分のキャリアとしては、サーバーサイドが一番長めで、
Java → PHP → Ruby, Python, Node.js あたりをふらふらしてきました。
直近1~2年はずっとRubyを書きつつ、
最近はSRE的な仕事が多めで、AWSの構築/保守/運用をしています。
想定読者
- これから Dart をやってみようかなと思っている人
- Dart がどういう言語なのかさくっと知りたい人
すでに Dart を書いている人向けではないです悪しからず。
本編
歴史
- JavaScriptの欠点を補うために2011年Googleが公開
- Chromeブラウザに組み込まれるはずだった
- 2015年断念し、Javascriptへコンパイルして使う方へ専念
- 2018年最も学ぶ価値のない言語1位になる
- 2018年12月 Google がモバイルSDK Flutter をリリース
- 2019年のランキングでは14位まで下がる
言語仕様
- クラスベースのオブジェクト指向言語
- 全てがオブジェクト
- 静的型付けができる
- ただ強制ではなく、動的型付けにすることもできる
-
public
,private
のキーワードはないが、プレフィックスに_
をつけることで private になる
インストール
とりあえず Mac に入れる。
$ brew tap dart-lang/dart
$ brew install dart
$ dart --version
Dart VM version: 2.7.0 (Fri Dec 6 16:26:51 2019 +0100) on "macos_x64"
2.7.0 が入った
Hello world
void main() {
print("Hello world");
}
$ dart hello.dart
Hello world
Javaっぽい。
public static
書かなくていい分楽。
文末セミコロンなきゃだめなのかー
Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List parameter for arguments.
https://dart.dev/guides/language/language-tour#the-main-function
main()
はエントリーポイントとなる関数で、必ずアプリケーションのトップレベルに置く必要がある。
引数として List<String>
を取る。このへんも Java と同じ。
void main(List<String> args) {
print(args[0]);
}
$ dart hello.dart moi!
moi!
エディタ
手軽にブラウザで試せるやつがある
DartPad
普段は Visual Studio Code 使うのでこのプラグイン入れる
Dart-Code
他も有名どころはだいたいある感じ。
文法
変数
型を明示しない場合は var
で宣言する
var name = 'Voyager I';
var year = 1977;
var antennaDiameter = 3.7;
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var image = {
'tags': ['saturn'],
'url': '//path/to/saturn.jpg'
};
型を指定する場合は String
とかをつける。
dynamic
は var
と同じ。
String name = 'Voyager I';
int year = 1977;
double antennaDiameter = 3.7;
List<String> flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
Map<String, dynamic> image = {
'tags': ['saturn'],
'url': '//path/to/saturn.jpg'
};
初期値を指定しない場合は null
になる。
int lineCount; // null
定数
final
か const
を使う。
final name = 'Bob';
final String nickname = 'Bobby';
2つのキーワードの違いは、一度だけ初期化されるか、コンパイル時かみたいなことらしい。
If you never intend to change a variable, use final or const, either instead of var or in addition to a type. A final variable can be set only once; a const variable is a compile-time constant. (Const variables are implicitly final.) A final top-level or class variable is initialized the first time it’s used.
詳しくは Final and const あたりを。
コレクション
List
, Set
, Map
あたり。
final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
'one': 1,
'two': 2,
'three': 3,
};
final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};
制御構文
特に目新しいところはない。Javaと一緒。
switch
もある。
if (year >= 2001) {
print('21st century');
} else if (year >= 1901) {
print('20th century');
}
for (var object in flybyObjects) {
print(object);
}
for (int month = 1; month <= 12; month++) {
print(month);
}
while (year < 2016) {
year += 1;
}
if
はワンライナーでも書ける。
var flg = true;
if (flg) print('Hello');
条件式は bool
の値しか取らない。
ここは JavaScript とかと違うところ。
Dart において true
は bool true
のみ。
// Error
var flg = 1;
if (flg) print('Hello');
こういうのはいける
var name = '';
if (name.isEmpty) print('Hello');
関数
戻り値の型を指定するのが推奨
int fibonacci(int n) {
if (n == 0 || n == 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
var result = fibonacci(20);
アロー =>
を使ったショートハンドも書ける。
無名関数扱うのに便利。
あと forEach(print)
みたいに引数として渡せる。
flybyObjects.where((name) => name.contains('turn')).forEach(print);
愚直に書くとこんな感じかな?
bool containsTurn(String name) {
return name.contains('turn');
}
flybyObjects.where(containsTurn).forEach((name) {
print(name);
});
インポート
ライブラリや別ファイルのインポート。
ライブラリのインストールは後述。
// Importing core libraries
import 'dart:math';
// Importing libraries from external packages
import 'package:test/test.dart';
// Importing files
import 'path/to/my_other_file.dart';
クラス
宇宙船
クラスで、名前
と 打ち上げ日
をプロパティとして持っている。
クラス名でコンストラクタを定義する。
class Spacecraft {
String name;
DateTime launchDate;
Spacecraft(this.name, this.launchDate);
}
上のコンストラクタはシンタックスシュガーで、同名のプロパティに値を割り当ててくれる。
これと同義。
Spacecraft(name, launchDate) {
this.name = name;
this.launchDate = launchDate;
}
名前付きのコンストラクタも定義できる。
: this(name, null)
はデフォルトのコンストラクタを続けて呼び出す super
的なやつ。
// Named constructor that forwards to the default one.
Spacecraft.unlaunched(String name) : this(name, null);
getter
はこんな感じ。もちろん setter
もある。
int get launchYear => launchDate?.year;
メソッドは特に解説要らないすね。
日付まわり使いやすそう。
void describe() {
print('Spacecraft: $name');
if (launchDate != null) {
int years = DateTime.now().difference(launchDate).inDays ~/365;
print('Launched: $launchYear ($years years ago)');
} else {
print('Unlaunched');
}
}
インスタンス生成も目新しいところはない。
var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5));
voyager.describe();
var voyager3 = Spacecraft.unlaunched('Voyager III');
voyager3.describe();
Nullまわりの便利演算子
?.
launchDate
が null
だったら null
を返す。
null
でなければ後続のメソッドを呼ぶ。
launchDate?.year
これと同義です。
(launchDate != null) ? launchDate.year : null;
Ruby の &.hoge
とか .try(:hoge)
みたいな感じか。
??=
左辺の値が null
だったら右辺の値を代入する。
null
でなければ何もしないみたいなやつ。
Rubyでいうところの ||=
int a; // The initial value of a is null.
a ??= 3;
print(a); // <-- Prints 3.
a ??= 5;
print(a); // <-- Still prints 3.
??
先の例で評価をそのまま返すパターン。
Rubyでいうところの ||
print(1 ?? 3); // <-- Prints 1.
print(null ?? 12); // <-- Prints 12.
変数展開
$変数名
で展開される。
Dart ではシングルクォートが推奨されているが、
シングルでもダブルでも展開される。
print('Launched: $launchYear ($years years ago)');
式を書きたいときは ${}
とする。
print('2 + 3 = ${2 + 3}');
継承
extends
キーワードで単一継承です。
class Orbiter extends Spacecraft {
num altitude;
Orbiter(String name, DateTime launchDate, this.altitude) : super(name, launchDate);
}
ミックスイン
with
キーワードでクラスにメソッドを生やせるやつ。
Ruby の extend
的な。
class Piloted {
int astronauts = 1;
void describeCrew() {
print('Number of astronauts: $astronauts');
}
}
class PilotedCraft extends Spacecraft with Piloted {
// ···
}
var piloted = PilotedCraft('Piloted Craft I', DateTime(2001, 10,3));
piloted.describeCrew();
これインスタンス変数のスコープとかどうなるのかなー。
どっち優先されるとかいろいろ考えないといけないやつかな
インターフェース
Dart には interface
というキーワードはない。
あらゆるクラスは暗黙的にインターフェースを定義している。
Dart has no
interface
keyword. Instead, all classes implicitly define an interface. Therefore, you can implement any class.
class MockSpaceship implements Spacecraft {
// ···
}
インターフェース使いたいならクラスが持つメンバーとメソッド全部実装しろよってことっぽい。
詳しくは implicit-interfaces
抽象クラス
これはそのままJavaっぽい。
インスタンス化できないクラス。
abstract class Describable {
void describe();
void describeWithEmphasis() {
print('=========');
describe();
print('=========');
}
}
非同期
async
/await
でやる
const oneSecond = Duration(seconds: 1);
// ···
Future<void> printWithDelay(String message) async {
await Future.delayed(oneSecond);
print(message);
}
Future
/then
を使った書き方もある
Future<void> printWithDelay(String message) {
return Future.delayed(oneSecond).then((_) {
print(message);
});
}
ストリームはこう扱う。
Stream<String> report(Spacecraft craft, Iterable<String> objects) async* {
for (var object in objects) {
await Future.delayed(oneSecond);
yield '${craft.name} flies by $object';
}
}
カスケード記法
..
でオブジェクトに対するメソッド呼び出しをチェーンできる。
オブジェクト(querySelector
)が返ってくるので、プロパティの classes
とかにアクセスできてる。
querySelector('#confirm')
..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
チェーンしやすい...!
これと同義です。
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
こういうことですね
// this returns value
myObject.someMethod()
// this returns reference to object
myObject..someMethod()
Optional positional parameters
[]
で引数をオプショナルにできる
int sumUp(int a, int b, int c) {
return a + b + c;
}
int total = sumUp(1, 2, 3);
int sumUpToFive(int a, [int b, int c, int d, int e]) {
int sum = a;
if (b != null) sum += b;
if (c != null) sum += c;
if (d != null) sum += d;
if (e != null) sum += e;
return sum;
}
int total = sumUptoFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
デフォルト値も設定できる。
int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
...
}
int newTotal = sumUpToFive(1);
print(newTotal); // <-- prints 15
Optional named parameters
キーワード引数の。
{}
シンタックスこれかよとは思うが。
void printName(String firstName, String lastName, {String suffix}) {
print('$firstName $lastName ${suffix ?? ''}');
}
printName('Avinash', 'Gupta');
printName('Poshmeister', 'Moneybuckets', suffix: 'IV');
void printName(String firstName, String lastName, {String suffix = ''}) {
print('$firstName $lastName ${suffix}');
}
例外
Javaと違って全部 unchecked exception
らしい。
これは必ず catch
しろよみたいなのがない。
Dart code can throw and catch exceptions. In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods don’t declare which exceptions they might throw, and you aren’t required to catch any exceptions.
Exception
, Error
の他に null
以外のオブジェクトならなんでも投げてよし。
throw Exception('Something bad happened.');
throw 'Waaaaaaah!';
例外の種類は on
キーワードで定義する。
catch
は省略できる。例外オブジェクトを参照したい時につける。
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
呼び出し元に伝播させるには rethrow
キーワードを使う。
finally
は普通にある。
Initializer lists
なんかコンストラクタで前処理必要なときに先に変換しておけるみたいなこと?
final
のフィールドはコンストラクタの中身が実行される前に初期化しないといけない事情があるから。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
Sometimes when you implement a constructor, you need to do some setup before the constructor body executes. For example, final fields must have values before the constructor body executes. Do this work in an initializer list, which goes between the constructor’s signature and its body:
開発中はアサーション置く場所として使えるねみたいなことも書いてある。
The initializer list is also a handy place to put asserts, which run only during development:
NonNegativePoint(this.x, this.y)
: assert(x >= 0),
assert(y >= 0) {
print('I just made a NonNegativePoint: ($x, $y)');
}
Factory constructors
継承してる親クラスから子クラスを透過的にインスタンス化できると。
factory
なんてキーワード初めて見たな。
class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) {
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
print('I don\'t recognize $typeName');
return null;
}
}
Const constructors
噛みそうな名前(笑)
If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a
const
constructor and make sure that all instance variables are final.
最初からイミュータブルなインスタンスとして生成する。
class ImmutablePoint {
const ImmutablePoint(this.x, this.y);
final int x;
final int y;
static const ImmutablePoint origin = ImmutablePoint(0, 0);
}
パッケージ
Dart のパッケージ管理ツールは pub
ここでパッケージが公開されてる
よくこのドメイン取れたなw
webやサーバーサイド開発なら stagehand
モバイル開発なら Flutter のツールを使ってわちゃわちゃやる感じらしい。
サードパーティのライブラリをインストールするためには、自分のアプリをまずパッケージ化しないといけない。
npm init
的なノリですね。
pubspec.yaml
というファイルが必要になる。
Stagehand
の入手から。
$ pub global activate stagehand
Warning: Pub installs executables into $HOME/.pub-cache/bin, which is not on your path.
You can fix that by adding this to your shell's config file (.bashrc, .bash_profile, etc.):
export PATH="$PATH":"$HOME/.pub-cache/bin"
Activated stagehand 3.3.5.
こんなメッセージが出たので言われた通りパスを通して実行すると、
どんな種類のテンプレートが作れるか表示される。
$ stagehand
とりあえず生成してみる。
$ stagehand console-full
ファイルやらディレクトリがわちゃわちゃ作成される。
依存関係をpubspec.yaml
の dependencies
に記述する。
dependencies:
vector_math: ^2.0.7
pubspec.lock
とは Gemfile
Gemfile.lock
的な関係なんだろう。
pub get
でインストールすると、import
して使えるようになる。
import 'package:vector_math/vector_math.dart';
void main() {
var x = Vector3.zero(); // Zero vector
var y = Vector4.all(4.0); // Vector with 4.0 in all lanes
print('x: $x');
print('y: $y');
x.zyx = y.xzz; // Sets z,y,x the values in x,z,z
print('x: $x');
print('y: $y');
}
スタイルガイドとか
軽く見ておく。
https://dart.dev/guides/language/effective-dart
命名
3種類の規則を使う
- UpperCamelCase
- loweCamelCase
- lowercase_with_underscores
クラスとか型定義には UpperCamel
class SliderMenu { ... }
class HttpRequest { ... }
typedef Predicate<T> = bool Function(T value);
ライブラリ、パッケージ、ファイル名などは lowercase_with_underscores
これは case-sensitive じゃないファイルシステムがあるから
library peg_parser.source_scanner;
import 'file_system.dart';
import 'slider_menu.dart';
メンバ、変数、パラメーターとかは lowerCamel
var item;
HttpRequest httpRequest;
void align(bool clearItems) {
// ...
}
定数は今は lowerCamel。
昔は SCREAMING_CAPS
だったらしいですね。(大文字+アンダースコア)
const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');
class Dice {
static final numberGenerator = Random();
}
フォーマット
dartfmt
でやる。
$ dartfmt -w hoge.dart
タブではなくスペース。
2つと4つどっちがデファクトなんだろう
他はこのあたり〜
https://github.com/dart-lang/dart_style/wiki/Formatting-Rules
Tips
プラクティス的なことはこのへん見れば良いと思います。
null
を false
にしたかったらこう、とか
optionalThing?.isEnabled ?? false;
コミットしてはいけないファイル
テスト
終わり
ざっとでしたが、Javaに根ざしたオブジェクト指向に、
スクリプト系(JavaScript, Ruby, Ptyhon)のエッセンスが入ってて使いやすそうな印象でした〜
次は Flutter 調べよ。