#note ## 環境構築 [https://dart.dev/get-dart](https://dart.dev/get-dart) [https://dart.dev/tools/jetbrains-plugin](https://dart.dev/tools/jetbrains-plugin) - Dart SDK path に `/opt/homebrew/opt/dart/libexec` [https://dartpad.dev/](https://dartpad.dev/) で playground を実行できる ## チュートリアル - [https://dart.dev/tutorials](https://dart.dev/tutorials) - [https://dart.dev/tutorials/server/get-started](https://dart.dev/tutorials/server/get-started) - [https://dart.dev/tutorials/server/cmdline](https://dart.dev/tutorials/server/cmdline) - [https://dart.dev/tutorials/server/fetch-data](https://dart.dev/tutorials/server/fetch-data) - [https://github.com/dart-lang/samples](https://github.com/dart-lang/samples) に dart が出しているサンプルがたくさんある - [A simple Dart HTTP server](https://github.com/dart-lang/samples/tree/main/server/simple) - [A Dart HTTP server that uses Cloud Firestore](https://github.com/dart-lang/samples/tree/main/server/google_apis) - [Command line app sample](https://github.com/dart-lang/samples/tree/main/command_line) - [Native compilation sample](https://github.com/dart-lang/samples/tree/main/native_app) - サンプルの中でも言語機能を紹介する目的のもの - [Enhanced enums samples](https://github.com/dart-lang/samples/tree/main/enhanced_enums) - [Extension methods samples](https://github.com/dart-lang/samples/tree/main/extension_methods) - [Null Safety](https://github.com/dart-lang/samples/tree/main/null_safety) ## 言語仕様 [https://dart.dev/language](https://dart.dev/language) [https://dart.dev/effective-dart](https://dart.dev/effective-dart) ### Package 構成 (lib について) [https://dart.dev/tutorials/server/fetch-data](https://dart.dev/tutorials/server/fetch-data) をリファクタリングした例 [https://dart.dev/tools/pub/create-packages](https://dart.dev/tools/pub/create-packages) にもある通り常に、 `lib/` がライブラリとなるらしい > As you might expect, the library code lives under the lib directory and is public to other packages. You can create any hierarchy under lib, as needed. By convention, implementation code is placed under lib/src. Code under lib/src is considered private; other packages should never need to import src/.... To make APIs under lib/src public, you can export lib/src files from a file that's directly under lib. とあるように `lib/src` は実装の詳細を記述していることが期待されている sh ``` . ├── CHANGELOG.md ├── README.md ├── analysis_options.yaml ├── bin │   └── main.dart ├── lib │   ├── data.dart │   └── http │   ├── error.dart │   └── http.dart ├── pubspec.lock ├── pubspec.yaml └── test └── fetch_test.dart ``` [https://dart.dev/tools/pub/create-packages#organizing-a-package](https://dart.dev/tools/pub/create-packages#organizing-a-package) には [shelf](https://github.com/dart-lang/shelf) の例が載っていて、テクニックとしてライブラリの利用者に対しては `lib/src` でパブリックにしていいもののみを `lib` 直下のファイルに `export` しているらしい (以下例) dart ``` export 'src/cascade.dart' show Cascade; export 'src/handler.dart' show Handler; export 'src/hijack_exception.dart' show HijackException; export 'src/middleware.dart' show Middleware, createMiddleware; export 'src/middleware/add_chunked_encoding.dart' show addChunkedEncoding; export 'src/middleware/logger.dart' show logRequests; export 'src/middleware_extensions.dart' show MiddlewareExtensions; export 'src/pipeline.dart' show Pipeline; export 'src/request.dart' show Request; export 'src/response.dart' show Response; export 'src/server.dart' show Server; export 'src/server_handler.dart' show ServerHandler; ``` ### Cascade notation カスケード記法 記法: `..` `?..` [https://dart.dev/language/operators#cascade-notation](https://dart.dev/language/operators#cascade-notation) dart ``` var paint = Paint(); paint.color = Colors.black; paint.strokeCap = StrokeCap.round; paint.strokeWidth = 5.0; // カスケード記法を使った上と同一のコード var paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; ``` Kotlin における `apply()` みたいな気持ちっぽい ### Pub workspaces [https://dart.dev/tools/pub/workspaces](https://dart.dev/tools/pub/workspaces) Dart で monorepo をうまく扱えるようにするもの また [Melos](https://github.com/invertase/melos) というサードパーティツールが古くからあり、 7系から依存管理については、 Pub workspaces に乗っかっている [https://melos.invertase.dev/](https://melos.invertase.dev/) を見ていけばどういう機能があるかはわかるが、一番重要なのは、以下のようにスクリプトを書けることで、特に `exec` という機能については全てのパッケージに入り込んだ上でそれぞれのパッケージに同じスクリプトが実行できるという便利さがある。 ルートディレクトリで1つのスクリプトを実行したい場合には `run` という機能を使う yaml ```yaml melos: scripts: test: # Dart テストを実行し、カバレッジレポートを生成するスクリプト # 詳細は https://github.com/dart-lang/test/tree/master/pkgs/test を参照 run: | # カバレッジデータを生成 dart test --coverage="./coverage" # coverage パッケージをグローバルに有効化 dart pub global activate coverage # カバレッジデータをフォーマットし、HTML レポートを生成 dart pub global run coverage:format_coverage --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage genhtml -o ./coverage/report ./coverage/lcov.info exec: concurrency: 1 # カバレッジデータを収集し、統合レポートを生成するスクリプト collect_coverage: run: | # Melos を使用して各パッケージのカバレッジファイルをリストアップ # パッケージ名の後に /coverage/lcov.info を追加する target_lcov_files=$(melos list --parsable | sed 's|$|/coverage/lcov.info|') # -a オプションを使用して、複数の lcov ファイルを統合 lcov -o ./coverage/combined.lcov $(echo "$target_lcov_files" | sed 's/^/-a /') # 統合されたカバレッジデータから HTML レポートを生成 genhtml ./coverage/combined.lcov --output-directory ./coverage/report open ./coverage/report/index.html ``` [https://github.com/invertase/melos/issues/566](https://github.com/invertase/melos/issues/566) にもあるように `exec` はオプションを指定したい時には `run` + `exec` を書くという文法で非常にわかりづらい ### Switch と Exhaustive [https://dart.dev/language/branches#exhaustiveness-checking](https://dart.dev/language/branches#exhaustiveness-checking) dart ``` // nullable も網羅性の1つ switch (nullableBool) { case true: print('yes'); case false: print('no'); } // Enum や sealed class も網羅性がある sealed class Shape {} class Square implements Shape { final double length; Square(this.length); } class Circle implements Shape { final double radius; Circle(this.radius); } double calculateArea(Shape shape) => switch (shape) { Square(length: var l) => l * l, Circle(radius: var r) => math.pi * r * r, }; ``` Switch が statement (文) なのか expression (式) で syntax が異なるらしい (不思議) - Cases do not start with the case keyword. - A case body is a single expression instead of a series of statements. - Each case must have a body; there is no implicit fallthrough for empty cases. - Case patterns are separated from their bodies using => instead of :. - Cases are separated by , (and an optional trailing , is allowed). - Default cases can only use _, instead of allowing both default and _. dart ``` // statement switch (charCode) { case slash || star || plus || minus: // Logical-or pattern token = operator(charCode); case comma || semicolon: // Logical-or pattern token = punctuation(charCode); case >= digit0 && <= digit9: // Relational and logical-and patterns token = number(); default: throw FormatException('Invalid'); } // expression (3.0 以降で出た機能らしい) token = switch (charCode) { slash || star || plus || minus => operator(charCode), comma || semicolon => punctuation(charCode), >= digit0 && <= digit9 => number(), _ => throw FormatException('Invalid'), }; ``` ### mixin [https://dart.dev/language/mixins](https://dart.dev/language/mixins) `with` で mix-in を利用することができる dart ``` mixin Piloted { int astronauts = 1; void describeCrew() { print('Number of astronauts: $astronauts'); } } // mixin を利用する class PilotedCraft extends Spacecraft with Piloted { // ··· } ``` `on` で mix-in が利用できるクラスを限定することができる dart ``` class Musician { musicianMethod() { print('Playing music!'); } } mixin MusicalPerformer on Musician { performerMethod() { print('Performing music!'); super.musicianMethod(); } } class SingerDancer extends Musician with MusicalPerformer { } main() { SingerDancer().performerMethod(); } ``` ### Implicit Interface [https://dart.dev/language/classes#implicit-interfaces](https://dart.dev/language/classes#implicit-interfaces) class は全て interface である ( `implicit interface` ) という考え方は特徴的な気がする > Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B's API without inheriting B's implementation, class A should implement the B interface. dart ``` void main() { print(greetBob(Person('Kathy'))); print(greetBob(Impostor())); } String greetBob(Person person) => person.greet('Bob'); // class のままで以下のように implements も extends もできる class Person { final String _name; Person(this._name); String greet(String who) => 'Hello, $who. I am $_name.'; } // implements (全ての元実装を実装しないとエラーとなる) class Impostor implements Person { String get _name => ''; String greet(String who) => 'Hi $who. Do you know who I am?'; } // extends (一部の実装だけでもよく元の実装を使うことができる) class Impostor extends Person { Impostor(): super(''); String greet(String who) => super.greet(who) + '(Impostor)'; } ``` 一応 explicit に interface を定義すること自体はできる (いつ使うんだろう??) [https://dart.dev/language/class-modifiers#interface](https://dart.dev/language/class-modifiers#interface) ### Async & Await [https://dart.dev/language/async](https://dart.dev/language/async) ### dynamic runtime まで型の評価を延期するには `dynamic` キーワードを使う [https://dart.dev/effective-dart/design#avoid-using-dynamic-unless-you-want-to-disable-static-checking](https://dart.dev/effective-dart/design#avoid-using-dynamic-unless-you-want-to-disable-static-checking) ### late [https://dart.dev/language/variables#default-value](https://dart.dev/language/variables#default-value) [https://dart.dev/language/variables#late-variables](https://dart.dev/language/variables#late-variables) dart ``` // 基本的には、 `late` をつけなくても dart の control-flow analysis で後で設定されるている場合には問題ない // しかし検出できない場合にはその判断を runtime に委ねる `late` をつけることでコンパイル可能になる late String description; void main() { description = 'Feijoada!'; print(description); } ``` ### Metadata `@Deprecated``@deprecated` `@override` `@pragma` のようなもの [https://dart.dev/language/metadata](https://dart.dev/language/metadata) `class` を普通に定義するだけでアノテーションになるのか dart ``` class Todo { final String who; final String what; const Todo(this.who, this.what); } // 利用 @Todo('Dash', 'Implement this function') void doSomething() { print('Do something'); } ``` ### Runes [https://dart.dev/language/built-in-types#runes-and-grapheme-clusters](https://dart.dev/language/built-in-types#runes-and-grapheme-clusters) 絵文字とかの Unicode の長さを扱えるらしい dart ``` import 'package:characters/characters.dart'; void main() { var hi = 'Hi 🇩🇰'; print(hi); print('The end of the string: ${hi.substring(hi.length - 1)}'); print('The last character: ${hi.characters.last}'); } ``` output txt ``` $ dart run bin/main.dart Hi 🇩🇰 The end of the string: ??? The last character: 🇩🇰 ``` ### Function - named parameters dart ``` // with named parameters void main() { doSomething(bold: true, hidden: false); } void doSomething({bool? bold, bool? hidden}) { // do something } // without named parameters void main() { doSomething(true, false); } void doSomething(bool? bold, bool? hidden) { // do something } ``` > If you don't provide a default value or mark a named parameter as required, their types must be nullable as their default value will be null: -> つまり required or default value がない場合には `bool` と宣言することができない dart ``` void main() { doSomething(); } void doSomething({bool? bold, bool? hidden}) { print(bold); // null print(hidden); // null } // required + default value void main() { doSomething(bold: true); } void doSomething({required bool? bold, bool? hidden = false}) { print(bold); print(hidden); } ``` ### Construcotors コンストラクタと呼ばれるものがたくさんある - Generative constructors - Named constructors - Redirecting constructors - 特殊系 - Default constructors - Constant constructors - Factory constructors - Redirecting factory constructors また以下の順番で実行される 1. initializer list 2. superclass's unnamed, no-arg constructor 3. main class's no-arg constructor dart ``` class Person { String? firstName; Person.fromJson(Map data) { print('in Person'); } } class Employee extends Person { // `super.fromJson(data)` を呼ばない場合には // Error: The superclass, 'Person', has no unnamed constructor that takes no arguments. Employee.fromJson(Map data) : super.fromJson(data) { print('in Employee'); } } void main() { var employee = Employee.fromJson({}); print(employee); // Prints: // in Person // in Employee // Instance of 'Employee' } ``` Generative constructors, Named constructors, Redirecting constructors dart ``` class Point { static const Point origin = Point(0, 0); double x; double y; // Generative constructors Point(this.x, this.y); // Named constructors Point.origin() : x = 0, y = 0; // Redirecting constructors Point.alongXAxis(double x) : this(x, 0); } ``` Default constructors > If you don't declare a constructor, Dart uses the default constructor. The default constructor is a generative constructor without arguments or name. Constant Constructor dart ``` class ImmutablePoint { static const ImmutablePoint origin = ImmutablePoint(0, 0); final double x, y; const ImmutablePoint(this.x, this.y); } ``` Factory constructors インスタンスがすでにある場合にキャッシュから返すなどする場合には `factory` を利用する 考え方としてはインスタンス化を含む部分をユーザーに委ねる機能という感じ? dart ``` class Logger { final String name; // _cache is library-private, thanks to // the _ in front of its name. static final Map<String, Logger> _cache = <String, Logger>{}; Logger._internal(this.name); factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); } } ``` Redirecting factory constructors 別のクラスにインスタンス化も含めて委譲するケースで利用 dart ``` factory Listenable.merge(List<Listenable> listenables) = _MergingListenable ``` ちなみに Constuctor のあとの `:` は `initializer list` と呼ぶらしい > Before the constructor body runs, you can initialize instance variables. Separate initializers with commas. [https://dart.dev/language/constructors#use-an-initializer-list](https://dart.dev/language/constructors#use-an-initializer-list) 多分 Constructor の処理の一環として実行してくれるみたいな機能っぽい dart ``` // Initializer list sets instance variables before // the constructor body runs. Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! { print('In Point.fromJson(): ($x, $y)'); } ``` ### Extension Methods dart ``` extension NumberParsing on String { int parseInt() { return int.parse(this); } } // 利用 print('42'.parseInt()); // Use an extension method. // 無名の extension も作ることができるが、名前解決でコンフリクトした時には解決すべき方法が限られる extension on String { bool get isBlank => trim().isEmpty; } ``` ### Extension types > An extension type is a compile-time abstraction that "wraps" an existing type with a different, static-only interface. とあるのでおそらく `inline class` のような形でコンパイルタイムで解決される型な気がする ### Callable objects `call()` を実装していると関数として実行することが可能らしい dart ``` class WannabeFunction { String call(String a, String b, String c) => '$a $b $c!'; } var wf = WannabeFunction(); var out = wf('Hi', 'there,', 'gang'); void main() => print(out); ``` dart ``` extension type IdNumber(int id) { // Wraps the 'int' type's '<' operator: operator <(IdNumber other) => id < other.id; // Doesn't declare the '+' operator, for example, // because addition does not make sense for ID numbers. } ``` # 言語進化 [https://dart.dev/resources/language/evolution](https://dart.dev/resources/language/evolution) ## Dart 3.0 特に Dart 3.0 には破壊的な変更が多く入っている [https://dart.dev/resources/dart-3-migration](https://dart.dev/resources/dart-3-migration) [Records](https://dart.dev/language/records) dart ``` var record = ('first', a: 2, b: true, 'last'); print(record.$1); // 'first' print(record.a); // 2 print(record.b); // true print(record.$2); // 'last' ``` のように複数の値を1つの変数の中に即席オブジェクトのように格納できる record の等価性は、あくまでも同じ shape かどうか dart ``` (int x, int y, int z) point = (1, 2, 3); (int r, int g, int b) color = (1, 2, 3); print(point == color); // 'true' ({int x, int y, int z}) point = (x: 1, y: 2, z: 3); ({int r, int g, int b}) color = (r: 1, g: 2, b: 3); print(point == color); // 'false' ``` [Patterns](https://dart.dev/language/patterns) Q. どこまでが 3.0 以降の機能なのかわかってない > In general, a pattern may match a value, destructure a value, or both, depending on the context and shape of the pattern. [https://dart.dev/language/pattern-types](https://dart.dev/language/pattern-types) を見る限り、 `subpattern1 || subpattern2` や `subpattern1 && subpattern2` から `var bar, String str, final int _` というようなものまで Pattern としているっぽい もちろん Map や Record なども Pattern [if-case](https://dart.dev/language/branches#if-case) dart ``` if (pair case [int x, int y]) { print('Was coordinate array $x,$y'); } else { throw FormatException('Invalid coordinates.'); } ``` [Guard clause](https://dart.dev/language/branches#guard-clause) dart ``` void main() { var pair = (2,1); switch (pair) { case (int a, int b) when a > b: print('a is greater than b'); } } ```