Subterranean Flower

プログラミング言語Dart基礎

Author
古都こと
ふよんとえんよぷよぐやま!!!

Dartの基本的な言語仕様を、実例と共にまとめてみた。

準備

開発環境

お手元のパッケージマネージャからinstallする、もしくは公式サイトからDartEditor(SDK同梱)あるいはSDK単体を自分で落とす。公式サイトからダウンロードできるDartEditorはEclipseベースのIDE。dart-sdk/binディレクトリに自分でパスを通しておく。

プログラムの実行

DartVM上でdartプログラムを実行する。

dart script_file.dart

DartEditorから起動する場合はEclipseとほぼ同じ。

基本

サンプル

だいたいこんな雰囲気。

void main() {
  var     dynVar = "真実は無く";         // 動的な宣言も
  String  strVar = "許されぬことなど無い"; // 静的っぽい書き方も

  // ラムダ式なども使える
  var joinStr = (str1, str2) => str1+str2;
  print("${joinStr(dynVar,strVar)} - Assassin's Creed");

  // クラスベースのOOPL
  var ezio = new Ezio();
  introduce(ezio);
}

// トップレベルでの関数定義
void introduce(Character char) {
  print("[名前]${char.name}\n[登場作品]${char.appearances.join(", ")}");
}

/*
 * 一般的なクラスベースのオブジェクト指向言語で使える要素は、
 * Dartでも大抵サポートされている。
 */
abstract class Character {
  final String       name;
  final List<String> appearances;

  Character(this.name, this.appearances);
}

class Ezio extends Character {
  Ezio():super("Ezio Auditore da Firenze", ["Assassin's Creed2", "Assassin's Creed Brotherhood"]);
}

ライブラリのインポート

import ‘lib_name’ as prefix でライブラリをインポートする。prefixを付与しない場合は同一名前空間上に展開されるので、衝突に注意。

import 'dart:math';                     // prefix無しimport
import 'dart:collection' as collection; // prefix付きimport

void main() {
    var rnd = new Random();           // prefixなし
    var que = new collection.Queue(); // prefixあり

    print(rnd.nextInt(10));
    print(que.isEmpty);
}

エントリポイント

トップレベルに存在するmain関数がエントリポイントとなる。プログラムはここから実行される。

void main() {
  print("ハロー!そしてグッドバイ!");
}

変数と型

Dartは動的型付け言語として振る舞うが、optionalとして型の指定が可能になっている。明示的に型を指定した場合はアノテーションとして機能し、可読性の確保やエディタでの入力補助、コンパイル時の警告などの恩恵を得ることができる。

DartVMに--checkedオプションを付けて起動すれば、処理速度と引き替えに実行時にも型チェックが行われる。DartEditorでは初期設定でcheckedモードで動作するようになっている。

varキーワードあるいは型を指定して変数を宣言することができる。String, int, double, bool, List, Mapなどの基本型がcoreライブラリにおいて定義されている。ジェネリクスの使用も可能。初期化されていない変数はnullであることが保証されている。

finalおよびconstキーワードを用いることにより、それぞれ定数およびコンパイル時定数を宣言することができる。

void main() {
  // primitives
  int    answer = 42;
  double pi     = 3.14;
  bool   isDart = true;
  List   words  = ["Fus", "Ro", "Dah"];
  Map    os     = {"Apple":"iOS", "Google":"Android"};

  // dynamic variables
  var x = "String value";
  var y = [1,2,3,"more"];

  // generics
  List<int>         intList  = [2,4,6,8,10];
  Map<int, String>  intToStr = {0:"zero", 1:"one", 2:"two"};

  // final and const
  final String fnl = "final value";
  const String cst = "constant value";

  print([answer, pi, isDart, words.first, os["Apple"]]);
  print([x, y]);
  print([intList[2], intToStr[2]]);
  print([fnl, cst]);
}

型のチェックとキャスト

暗黙のアップキャストのほか、Dartではダウンキャストも暗黙に行われる。checkedモードでの動作時は、無効なダウンキャストに対して例外がthrowされる。

明示的なキャストを行う場合はas演算子を使用する。

void main() { 
  // isによる型チェック
  print("str" is String);

  // 暗黙のダウンキャスト
  num n = 3.14;
  int x = n;

  // asによるキャスト
  var splitStr = (arg) => (arg as String).split("・");
  print(splitStr("エツィオ・アウディトーレ・ダ・フィレンツェ"));

  // よくある int <-> double はasではキャストできない
  int    val = 10;
  double dVal = val.toDouble();
}

関数

DartではJavaScriptなどと同様にトップレベルに関数を直接記述することができる。通常の記法の他、ラムダ式もサポートされる。

一般的な引数に加え、省略可能パラメータおよび名前付きパラメータが指定できる。省略可能なパラメータのデフォルト値を省略した場合、デフォルト値はnullになる。

全ての関数はFunction型のオブジェクトとして変数に代入して扱うことが可能である。

void main() {
  // call function
  print(sum(5,6));

  // optional parameters
  greet("Booker DeWitt");
  greet("Booker DeWitt", "Bring us the girl and wipe away the debt.");

  // optional named parameters
  arrowInTheKnee();
  arrowInTheKnee(lang:"ja");

  // lambda expression
  var louden = (String str) => str+"!!!!";
  print(louden("It's Gomez Time"));

  // high-order function
  print(twice(louden, "It's Gomez Time"));
}

// function
int sum(a, b) {
  return a+b;
}

// optional parameters
void greet(name,[additional="Nice to meet you."]) {
  print("Hi, $name. $additional");
}

// optional named parameters
void arrowInTheKnee({lang:"en"}) {
  if(lang=="en") { print("I used to be an adventurer like you. Then I took an arrow in the knee..."); }
  else if(lang=="ja") {print("昔はお前のような冒険者だった。膝に矢を受けてしまって…"); }
}

// high-order function
dynamic twice(dynamic function(Object o), Object x) {
  return function(function(x));
}

制御文

for, for-in, while, do-while, switchなどが利用可能。

void main() {
  var cond  = "GOOD";
  var list  = new List();
  var x     = 10;

  // if-else
  if(cond=="GOOD")      { print("good"); }
  else if (cond=="BAD") { print("bad"); }
  else                  { print("else"); }

  // switch
  switch(cond) {
    case "GOOD":
      print("good");
      break;
    case "BAD":
      print("bad");
      break;
    default:
      print("default");
  }

  // for
  for(var i=1; i<50; i++) { list.add(i); }

  // for-in
  for(var elem in list) {
    if(elem%15==0)      { print("FizzBuzz"); }
    else if(elem%5==0)  { print("Buzz"); }
    else if(elem%3==0)  { print("Fizz"); }
    else                { print(elem); }
  }

  // while
  while(true) {
    x /= 2;
    print("x=${x}");
    if(x<0.05) { break; }
  }

  // do-while
  do {
    x *= 2;
    print("x=${x}");
  } while(x<=10);
}

例外処理

try-catch-finallyによる例外処理が可能。例外の型はonで指定する。

void main() {
  var list = [1,2,3];
  var val;

  try { val = list.firstWhere((e)=>e>5); }  // 5より大きい要素を返す。無い場合はStateErrorを投げる。
  on StateError { val=0; }                  // StateError時の処理
  on Exception catch(e) { print(e); }       // その他の例外の処理
  finally { print(val); }
}

クラス

一般的なOOPLと同じく、クラスの定義・継承などが可能。public/privateのようなアクセス修飾子は無いが、メンバ名の先頭に_(アンダースコア)を付けるとprivateメンバとして扱われる。privateメンバは同一ライブラリ内でのみアクセスが可能になる。また、get/setキーワードを用いることでgetter/setterの定義ができる。

void main() {
  var content         = new HorseArmorPack();
  content.description = "By Azura! By Azura! By Azura!";

  print("Name: ${content.name}, Price: \$${content.price}, Description: ${content.description}");
}

class Content {
  var _name;       // private
  var _price;
  var description; // public

  String get name   => _name;  // getter
  double get price  => _price;
  String toString() => _name;
}

class HorseArmorPack extends Content {
  HorseArmorPack() {
    _name       = "Horse Armor Pack";
    _price      = 1.99; 
    description = "Protect your horse.";
  }
}

演算子オーバーロード

operatorキーワードを使って演算子のオーバーロードができる。

void main() {
  var vec1 = new Vector([1,2,3]);
  var vec2 = new Vector([5,6,7]);
  var vec3 = vec1 + vec2;
  vec3[2] = 50;
  print(vec3);
}

class Vector {
  final List<num> values;
  Vector(this.values);

  num    operator [](int index) => values[index];
  void   operator []=(int index, num value) { values[index] = value; }
  Vector operator +(Vector vec) {
    if(length != vec.length) { throw new Exception(); }

    var newVal = new List<num>();
    for(var i=0; i<length; i++) {
      newVal.add(this[i]+vec[i]);
    }

    return new Vector(newVal);
  }

  int get length => values.length;
  String toString() => values.toString();
}

コンストラクタ

Dartにはいくつかの種類のコンストラクタが用意されており、目的に応じて使い分けることができる。引数付きコンストラクタにおいて、引数名をthis.fieldNameにするとフィールドへの代入処理が自動で行われる。

generativeコンストラクタ

普通のコンストラクタ。

void main() {
  var p = new Island("Tropico");
  print(p.name);
}

class Island
{
  final String name;

  // generativeコンストラクタ
  Island(this.name);
}

namedコンストラクタ

名前付きコンストラクタ。Dartではメソッドのオーバーロードは認められておらず、目的に応じた名前付きコンストラクタを用意することが推奨されている。

void main() {
  var personA = new Person("Satori", "Komeiji");
  var personB = new Person.fromString("Koishi Komeiji");

  for(Person p in [personA, personB]) {
    print("[Fullname] ${p.toString()}");
    print("  [FirstName] ${p.firstName}");
    print("  [LastName]  ${p.lastName}");
  }
}

class Person {
  String firstName;
  String lastName;

  // generativeコンストラクタ
  Person(this.firstName, this.lastName);

  // namedコンストラクタ
  Person.fromString(String str) {
    var names = str.split(" ");
    firstName = names[0];
    lastName  = names[1];
  }

  String toString() { return [firstName, lastName].join(" "); }
}

constantコンストラクタ

コンパイル時定数オブジェクトを返す。メンバ変数はすべてfinalである必要があり、コンストラクタにはconstを付け、インスタンス生成にもnewではなくconstを使う。

void main() {
  var imt = const Immutable(2,3);
  print(imt.x);
}

class Immutable {
  final x;
  final y;
  const Immutable(this.x, this.y);
}

factoryコンストラクタ

factoryコンストラクタ自身はインスタンスを生成しない。よってfactoryコンストラクタはインスタンスをreturnする必要がある。名前こそfactoryではあるが、generativeコンストラクタと同じくnewキーワードで呼び出す。

void main() {
  var singleton = new Singleton();
  print(singleton.str);
}

class Singleton {
  static var _instance;
  String str;

  //factoryコンストラクタ
  factory Singleton() {
    if(_instance==null) { _instance = new Singleton._internal(); }
    return _instance;
  }

  //内部からの呼び出し用namedコンストラクタ
  Singleton._internal() { str = "singleton."; }
}

リダイレクト

コロン区切りで「コンストラクタ: リダイレクト関数」と書くことで、コンストラクタの処理をリダイレクトできる。実行順はリダイレクト先→コンストラクタ本文となる。Javaなどとは違い、superコンストラクタはこの方法でしか呼び出せない。

void main() {
  var rect = new Rectangle(1,1,5,10);
}

class Shape {
  final num x;
  final num y;
  Shape(this.x, this.y){ print("Super Constructor"); }
}

class Rectangle extends Shape {
  final num width;
  final num height;

  // スーパークラスへリダイレクト
  Rectangle(x,y,this.width,this.height):super(x,y) {
    // ここでsuper(x,y);するとエラー
    print("Sub Constructor");
  }
}

初期化リスト

リダイレクトと同様にして、フィールドの初期化処理を指定することができる。finalが指定されたフィールドの初期化はここで行うことができる。

void main() {
  var rect = new Rectangle(1,1,5,10);
}

class Shape {
  final num x;
  final num y;
  Shape(this.x, this.y){ print("Super Constructor"); }
}

class Rectangle extends Shape {
  final num width;
  final num height;

  // 初期化リストを用いたフィールドの初期化
  Rectangle(x, y, width, height):this.width=width, this.height=height, super(x,y) {
    print("Sub Constructor");
  }

抽象クラスとインターフェイス

abstractキーワードを用いることで抽象クラスを宣言できる。interfaceキーワードは存在せず、全てのクラスが暗黙のinterfaceを持っている。

void main() {
  var mew    = new Mew();
  var mewtwo = mew.clone();

  print(mewtwo);
}

// 抽象クラスの定義。暗黙的にインターフェイスを持つ。
abstract class Cloneable<T> {
  T clone();
}

// Cloneableクラスの持つインターフェイスを実装
class Mew implements Cloneable<Mew> {
  Mew clone() => new Mew();
}

その他

メソッドのカスケード

単一のオブジェクトについて複数の操作を連続して行う場合、Dartではカスケード演算子(..)を用いることで簡潔に記述することができる。

void main() {
  var list = new List()
                    ..add(1)                     // 1を加える
                    ..add(2)                     // 2を加える
                    ..addAll([3,4,5,6,7,8,9,10]) // 3-10を加える
                    ..fillRange(0, 3, 99)        // 0番目から2番目までを99にする
                    ..[3] = 0                    // 3番目を0にする
                    ..removeLast();              // 最後の要素を取り除く
  print(list);
}

ブラウザ上での動作

2013年9月18日現在、DartVMを搭載したブラウザはDartiumを除いて存在せず、主要なブラウザにおいてDartのコードを走らせることはできない。したがってDartSDKに含まれるdart2jsによりJavaScriptに変換し、動作させることになる。

dart2js

dart2js dart_script.dart -o dart_script.dart.js --minify

minifyオプションを付けないとファイルサイズが膨れあがるので注意。DartEditorを使う場合、dart2jsの設定は、プロジェクトを右クリック→Properties→Dart Settingsから変更できる。

ページのロードを待ってから実行する場合、scriptタグにdefer属性を付ける必要がある。

<script src="./dart_script.dart.js" defer></script>

dart:html

dart:htmlをimportすることでDOMの操作などが行えるようになる。

import 'dart:html';

void main() {
  var elem  = querySelector("#page-title"); // Elementの取得
  var eList = querySelectorAll(".item");    // ElementListの取得

  for(Element e in eList) { print(e.text); }
}