kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

ひとりDartクイックツアー

f:id:kun432:20201213011522p:plain

Flutterをやるにあたり、Dart全然わかってないのもあれだなということで、まずは基本的なところを写経。

目次

main関数

void main() {
  print("Hello World");
}
  • 起動はvoid main()から
  • 行末はセミコロンが必要

コメント

// 1行コメント

/*
複数行コメント
*/

void main() {
  print(/* こんな書き方もできるみたい */ 'Hello World');
}
  • 1行は//、複数行は/* 〜 */
  • /* 〜 */を途中に差し込むこともできるみたい

変数

void main() {
  var a = 1;
  var b = 2;
  var c = a + b;
  var d;
  var greeting = "Hello";

  print(c);                         // 3
  print(d);                         // null
  print("$greeting World");         // "Hello World"
  print("${greeting * 2} World");   // "HelloHelloHello World"
}
  • varで宣言した場合は型推論される
  • 宣言と同時に初期値を設定可
  • 初期値を設定しない場合はnull
  • $で変数の値を展開して埋め込める
  • ${ 〜 }{}内の処理を展開して埋め込める
void main() {
  var a = 1;
  print(a);                         // 1
  a = 2;
  print(a);                         // 2
  a++;
  print(a);                         // 3
}
  • 型が同じなら再代入も可能

void main() {
  int a = 1;
  int b = 2;
  int c = 0xFF;
  double d = 3.0;
  String e = "Hello";
  String f = " World";

  print(a); // 1
  print(c); // 255
  print(a + b); // 3
  print(a + d); // 4.0
  print(e); // "Hello"
  print(e + f); // "Hello World"
}
  • 型を指定して宣言
void main() {
  int a = 1;
  print(a);
  a = 2.0;
  print(a);
}
  • 型が異なる場合の再代入はエラー
sample.dart:4:7: Error: A value of type 'double' can't be assigned to a variable of type 'int'.
  a = 2.0;
      ^

int/double(数値)

void main() {
  int a = 1;
  int c = 0xFF;

  print(a); // 1
  print(c); // 255
}
  • 16進数は0xをつける
void main() {
  double a = 1.0;
  double b = 0.000314e4;
  double c = 31400e-4;

  int d = 3;
  print(a);     // 1.0
  print(b);     // 3.14
  print(c);     // 3.14
  print(d * a); // 3.0
}
  • int * doubleで計算してキャストさせることも

String(文字列)

void main() {
  String a = "Hello";
  String b = "World";

  String c = """Hello
World""";

  String d = "${a} ${b}";
  String e = '${a} ${b}';

  print(a + " " + b);       // "Hello World"
  print((a + " ") * 3 + b); // "Hello Hello Hello World"
  print(c);                 // "Hello
                            // World"
  print(d);                 // "Hello World"
  print(e);                 // "Hello World"
}
  • UTF-16
  • 改行を含む文字列はクオート3つ
  • シングルクォートとダブルクォートの違いはないっぽい
  • 文字列の計算も他の言語と同じ感じ

bool(真偽)

void main() {
  bool a = true;
  bool b = false;

  print(a); // true
  print(b); // false
}

List(配列)

void main() {
  List<String> a = ["John", "Paul", "George", "Ringo"];
  List<int> b = [2, 5, 4, 1, 3];
  var c = ["a", "b", "c"];

  print(a[0]);    // "John"
  print(a[1]);    // "Paul"
  print(a.last);  // "Ringo"

  b.add(6); print(b);               // [2, 5, 4, 1, 3, 6]
  b.insert(0, 0); print(b);         // [0, 2, 5, 4, 1, 3, 6]
  b.sort((a,b) => a - b); print(b); // [0, 1, 2, 3, 4, 5, 6]

  a.forEach((i) => print(i));  // "John
                               //  Paul
                               //  George
                               //  Ringo"

  var d = b.map((i) => i * 2);
  var e = b.map((i) => i * 2).toList();
  print("${b.runtimeType} : ${b}");  // List<int> : [0, 1, 2, 3, 4, 5, 6]
  print("${d.runtimeType} : ${d}");  // MappedListIterable<int, int> : (0, 2, 4, 6, 8, 10, 12)
  print("${e.runtimeType} : ${e}");  // List<int> : [0, 2, 4, 6, 8, 10, 12]
}
  • <>内に要素の型を指定する。指定せずに宣言も可能
  • インデックス指定。-1は使えなかった。.lastメソッドとかlst.length-1をインデックスに指定したりするみたい。
  • addとかmapとか。
  • sortやforEach, mapはコールバックに渡す。mapでreturnするとListじゃなくてMappedListIterableになる。toList()してあげる。

Set(重複不可な配列)

void main() {
  Set<int> a = {1,2,3,4,5};

  //a.add(1);          // 重複するのでエラー
  a.add(6); print(a);  // {1, 2, 3, 4, 5, 6}

  //print(a[0]);          // リストじゃないのでインデックスで呼び出せない
  print((a.toList())[0]); // 1 (toListしてからインデックスで呼び出す)
}
  • 重複不可なリスト
  • 順番がないのでインデックスで呼び出せない。toListすれば呼び出せる。多分用途が違う。

Map(マップ)

void main() {
  Map<String,String> a = {
    "Backing Guitar": "John",
    "Bass Guitar"   : "Paul",
    "Lead Guitar"   : "George",
    "Drum"          : "Pete",
  };

  print(a['Drum']); // "Pete"

  a.remove('Drum');
  a["Drum"] = "Ringo";

  Map<String,String> b = {
    "Manager": "Brian",
    "Producer": "Martin",
  };

  a.addAll(b);
  a.forEach((k,v) => print("${k}: ${v}"));
  // => Backing Guitar: John
  //    Bass Guitar: Paul
  //    Lead Guitar: George
  //    Drum: Ringo
  //    Brian: Manager
  //    Martin: Producer

  print(a.containsKey('Keyboard'));    // false
  print(a.containsKey('Bass Guitar')); // true
}
  • キーと値に型が必要
  • ネスト
kun432-mbp2016:dart_sandbox kun432$ cat eee
void main() {
  Map<String,Map<String,String>> a = {  // 多分ここの型指定がよくない。varとかで型推論させても同じ。
    "Backing Guitar": {
      "Name": "John",
    },
    "Bass Guitar"   : {
      "Name": "Paul",
    },
    "Lead Guitar"   : {
      "Name": "George",
    },
    "Drum"          : {
      "Name": "Ringo",
    }
  };
  print(a['Backing Guitar']['Name']);  // Error: Operator '[]' cannot be called on 'Map<String, String>?' because it is potentially null.
}
void main() {
  dynamic a = {               // dynamicを使うとOK。`Map<String,dynamic>`でもOK。これが正しいのかはわからない。
    "Backing Guitar": {
      "Name": "John",
    },
    "Bass Guitar"   : {
      "Name": "Paul",
    },
    "Lead Guitar"   : {
      "Name": "George",
    },
    "Drum"          : {
      "Name": "Ringo",
    }
  };
  print(a['Backing Guitar']['Name']);  // "John"
}
  • dynamicは途中で型が変わっても良い、varは初期化時に型が決まるので以降は違う型を入れれない、らしい。
  • 多分そういうことじゃなくて、わかってないだけ。これぐらいのものならもっときちんと型定義できる気がする・・・

定数

void main() {
  final int a = 1;
  // a = 2; // => Error: Can't assign to the final variable 'a'.
  const int b = 1;
  // b = 2; // => Error: Can't assign to the const variable 'b'.

  final List<int> c = [1,2,3,4,5];
  // c.add(6); // => OK。つまり変数のメモリ領域の中身までは関与しない。
  const List<int> d = [1,2,3,4,5];
  // d.add(6); // => Unsupported operation: Cannot add to an unmodifiable list
               //    変数のメモリ領域の中身も変更不可。

}
  • final/constの違いは上記

制御構文

if

void main(List<String> args) {

  var a = args[0].toLowerCase();

  if (a == "john") {
    print("John is a great Rock'n Roller!");
  } else if (a == "paul") {
    print("Paul is a great Pop Maker!");
  } else if (a == "george") {
    print("George is a greate slide guitarlist!");
  } else if (a == "ringo") {
    print("Ringo's drum gives rock-solid back-beat!");
  } else {
    print("${a}? who?");
  }
}
  • コマンドラインでやってみた。
void main() {

  int a = 1;
  String b = "hoge";

  if (a) print(true); // Error: A value of type 'String' can't be assigned to a variable of type 'bool'.
  if (b) print(true); // Error: A value of type 'String' can't be assigned to a variable of type 'bool'.
}
  • ifはほんとにboolしか取れない。

switch

void main(List<String> args) {

  var a = args[0].toLowerCase();

  switch (a) {
    case 'john':
      print("John is a great Rock'n Roller!");
      break;
    case 'paul':
      print("Paul is a great Pop Maker!");
      break;
    case 'george':
      print("Ringo's drum gives rock-solid back-beat!");
      break;
    case 'ringo':
      print("George is a greate slide guitarlist!");
      break;
    default:
      print("${a}? who?");
  }
}
  • breakの省略はできない(エラー)
void main(List<String> args) {

  var a = args[0].toLowerCase();

  switch (a) {
    case 'john':
      print("John is a great Rock'n Roller!");
    case 'paul':
      print("Paul is a great Pop Maker!");
    case 'george':
      print("George is a greate slide guitarlist!");
    case 'ringo':
      print("Ringo's drum gives rock-solid back-beat!");
      break;
    default:
      print("${a}? who?");
  }
}
// => Error: Switch case may fall through to the next case.
//    case 'john':
//    ^
//    case 'paul':
//    ^
//    case 'george':
  • caseが空ならOK
void main(List<String> args) {

  var a = args[0].toLowerCase();

  switch (a) {
    case 'john':
    case 'paul':
    case 'george':
    case 'ringo':
      print("The Beatles are great!");
      break;
    default:
      print("${a}? who?");
  }
}
  • continueとラベルを使う
void main(List<String> args) {

  var a = args[0].toLowerCase();

  switch (a) {
    case 'john':
      print("John is a great Rock'n Roller!");
      continue PAUL;
    PAUL:
    case 'paul':
      print("Paul is a great Pop Maker!");
      continue GEORGE;
    GEORGE:
    case 'george':
      print("George is a greate slide guitarlist!");
      continue RINGO;
    RINGO:
    case 'ringo':
      print("Ringo's drum gives rock-solid back-beat!");
      break;
    default:
      print("${a}? who?");
  }
}

for

void main() {
  for (var i = 0; i < 10; i++) {
    print(i);
  }
}

while/do while

void main() {
  var i = 0;
  while (i < 10) {
    print(i);
    i++;
  }
}
void main() {
  var i = 0;
  do {
    print(i);
    i++;
  } while (i < 10);
}

break/continue

void main() {
  var i = 1;
  var j = 10;
  while (true) {
    print(i);
    i++;
    if (i > j) break;
  }
}
// => 1 2 3 4 5 6 7 8 9 10
void main() {
  for (var i = 1; i <= 10; i++) {
    if (i % 2 == 1) continue;
    print(i);
  }
}

// => 2 4 6 8 10

for in/forEach

void main() {
  List<String> a = ["John", "Paul", "George", "Ringo"];
  a.forEach((i) => print(i));
}
// => John Paul George Ringo
void main() {
  List<String> a = ["John", "Paul", "George", "Ringo"];
  for (var i in a) {
    print("${i} is great!");
  }
}
// => John is great! Paul is great! George is great! Ringo is great!

関数

基本

bool isOdd(int number) {
  if (number % 2 == 1) return true;
  return false;
}

void main(List<String> args) {

  int a = int.parse(args[0]);
  print(isOdd(a));

}

-戻り値の型 関数名(引数の型 引数) { ... }。型アノテーション推奨。

bool isOdd(int number) =>number % 2 == 1;
  • 上記だと1行で書ける。=> expr{return expr;}と同じこと。
void echo() => print("odd checker");

void main() {
  echo();                                   // "odd checker"
}
  • 戻り値なしはvoid
String repeat(String string, int number) {
  return "${string} " * number;
}

void main(List<String> args) {

  String a = args[0];
  int b    = int.parse(args[1]);

  print(repeat(a, b));
}
  • 複数の引数はカンマ区切り
int tashizan({int num1 = 0, int num2 = 0}) {
  return num1 + num2;
}

void main(List<String> args) {

  int c    = int.parse(args[0]);
  int d    = int.parse(args[1]);

  print(tashizan(num1: c, num2: d));
}
  • 関数の引数を{}で囲むと仮引数を仮引数名: 値で指定できる。
  • 一つ前と同じ例にしようとしたらnull safetyでうまくいかなかった・・・
String repeat(String string, {int number = 1}) {
  return "${string} " * number;
}

void main(List<String> args) {

  String a = args[0];
  int b    = int.parse(args[1]);

  print(repeat(a, number: b));
}
  • 引数にデフォルト値を指定する場合は {}でくくってで指定
  • 引数の指定も仮引数名: 値になるので注意
  • null safetyを回避するにはデフォルト値の設定は必須になるっぽい

無名関数

void main() {
  List<String> a = ["John", "Paul", "George", "Ringo"];
  a.forEach((i) => print(i));
}
  • イテレータのコールバックで使う事が多い。
Function printName = (String i) {
  print("${i}");
};

void main() {
  List<String> a = ["John", "Paul", "George", "Ringo"];
  a.forEach( (i) {
    printName(i);
  });
}
  • 上と等価
Function printName = (String i) => print("${i}");
  • 処理が1行なら、関数も1行で書ける
void main() {
  var multiply = (int num) => print(num * 2);
  List<int> list = [1,2,3];
  list.forEach(multiply);
} // => 2 4 6
  • 関数の引数として関数を渡せる

スコープ

String a = "one";

void main() {
  String a = "two";
  print(a);   // "two"

  void print_a() {
    String a = "thee";
    print(a);   // "three"
  }
  print_a();
  {
    String a = "four";
    print(a);   // "four"
  }
}
  • { ... }のブロックで単純にスコープが分かれる。

クロージャ

Function tashizan(int x) {
 return (int i) => x + i;
}

void main() {
  var add1 = tashizan(1);
  var add2 = tashizan(2);
  var add3 = tashizan(3);

  print(add1(1)); // 2
  print(add2(2)); // 4
  print(add3(3)); // 6
}
  • 関数の戻り値に関数を返す
Function outer() {
  int x = 10;
  return () => x++;
}

void main() {
  var func1 = outer();
  print(func1());  // 10
  print(func1());  // 11
  print(func1());  // 12
}
  • やっぱりクロージャむずい><

assert

void main() {
  int a = 10;
  assert(a < 5);
  print(a);
}
  • コマンドラインだと--enable-assertsをつけて実行する
$ dart --enable-asserts sample.dart
Unhandled exception:
'file:///Users/xxxxx/repository/dart_sandbox/sample.dart': Failed assertion: line 3 pos 10: 'a < 5': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
#2      main (file:///Users/kun432/repository/dart_sandbox/fff:3:10)
#3      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:311:19)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
  • productionだと無視される

演算子

四則計算とか比較は他の言語とほとんど同じ。特徴的なやつだけ。

  • 型チェック
void main() {
  int a = 10;

  if(a is int) print(true);        // true
  if(a is! String) print(true);     // true
}

例外

void main(List<String> args) {

  String a = args[0].toLowerCase();

  try {
    if (a == 'john' || a == 'paul' || a == 'george' || a == 'ringo') {
      print('They are great!');
    } else if(a == 'martin' || a == 'brian') {
      throw FormatException('They are great too, but not a member.');
    } else if (a == 'mick' || a == 'keith' || a == 'ron' || a == 'charlie') {
      throw Exception('They are great too, but not fab four.');
    } else  {
      throw ('${a}? who?');
    }
  } on FormatException catch (e) {
      print('Error1: ${e}');
  } on Exception catch (e) {
      print('Error2: ${e}');
  } catch (e) {
      print('Unknown error: ${e}');
  }
}
  • try ~ catchも普通。
    • tryの中でthrowを投げて、catchで受ける
    • on 〜Exception catchで特定の例外を受ける
    • on Exception catchですべての例外を拾う
    • catchでエラーも含めて拾う
  • catch (e)でエラー内容を拾う、catch (e, s)でスタックトレースも拾える

まとめ

続きはまた更新します。

参考にさせていただいた書籍・サイト