ntaoo blog

主にDart, Flutter, AngularDartについて書いていきます。ときどき長文。日本語のみ。Twitter: @ntaoo

2018年のDart言語の振り返り

2019年初に2018年のDartを振り返ってみる。僭越ながらDart advent calendar 25日目の記事だが、大幅に超過して新年になってしまった。申し訳ない。そして大した内容ではない。個人の回顧です。

Dart 2のリリースと、Dart 1の回顧

Dart 1を回顧するに、以下の特徴をもった言語だったと思う。

  • VMを活かした動的言語であること
  • VM前提の動的言語の表現力、進化を妨げないための、選択換装可能な型システム (言語が型システムに依存するのではなく、型システムが言語に依存する)
  • エディタ、デバッガ(等々)が統合された開発体験などの、Smalltalkを目標とした環境の提供(が目標)
  • ライブラリや開発環境などを整備し、言語のカタログスペックでなく実際のDeveloper Experienceが優れていること
  • 世の中の圧倒的多数を占めるCとJavaプログラマーに学習コストとストレスなく使ってもらうため、C系(Java系)の構文であること
  • 他言語で良いとされている機能を単にカタログスペックを埋めるように採用するようなことはせず、言語仕様の無駄な肥大化を抑えること
  • JavaScriptに無理なくコンパイルできること

Dart 1時代のコアメンバーの経歴や発言、Snapshotの追加や独自統合開発環境(DartEditorやCDE)の提供の試みなどを観察するに、Dart 1によって、Javaの皮を被ったSmalltalkをWebにもたらそうという意思が強く伝わってきていた。そのような野心的な構想に、どうなるのかと興味を惹かれ続けてきたが、その心づもりの言語は結局さまざまな環境要因により、当時のWebコミュニティでは人気を得ることは叶わなかった。これは本当に残念なことだった。

Dartが、Webに加えて他のプラットフォームへの適用の可能性を模索してきた中で結実したもののひとつがFlutterだが、その過程において、iOSなどのプラットフォームの制約事項によりDart言語がVMを前提にできなくなり、AoTコンパイル後のパフォーマンスとスペースを少しでも上げるために、型システムへの方針を変更してAoTコンパイル時に積極的に型情報を利用するものに刷新したものがDart 2であるという理解をしている。 それに伴い、型システムでなくランタイムからのフィードバックに依存する動的言語としての側面はなりをひそめ、AoTコンパイルコンパイルメタプログラミングの技法が重視されるようになった。dart:mirrorは実質的にVM環境でないと使用できないというややもどかしい状況になっている。ただし、Flutterでも開発時や、CLIやサーバーサイドはVMで動作する。

2018年にDart 2がリリースされるまで、宣伝を控えて年単位で大改装が続けられていたが、Dart 2を決意して無事リリースされた現在は、マイナーな機能改善に着手できる段階に達したのだろう。リンクは張らないが、言語チームはいろいろな改善を検討していることがGitHubから観察できる、と書いていたら、うまく日本語でまとめられていたので、リンクを張っておく。

Dartで鋭意検討中の新機能

しばらくは、Flutter中心のフィードバックを経て、ユースケースベースで言語機能の改善が続けられていくのだろう。

Dart 1では、主にScalaやKotlinあたりが優れた言語であると信じるプログラマーから、それらの(というか、Java)言語に表面上似ていても独自のビジョンをもって優先順位を決めていたこの言語に対して、批判がたびたび聞こえてきていたし、現在も、Flutterの人気爆発によりDartが注目されたため、それが再び散見されるようになった。

Dart言語は、評価が定まっていない仕様を軽率に採用してあとから取り除くこともできず、結果として言語仕様やコアなエコシステムが乱雑になる事態を警戒してきたことが観察できる。たとえ、Kotlin、Swift、ScalaJavaScriptなどの他の言語で良いとされて導入されているアイデアであっても、言語仕様や標準ライブラリへの追加には、極めて慎重で注意深い態度を示してきた。

たとえば、言語マニアからまれに批判される、パターンマッチングが無いことなどについても、長い議論の末に、極めて意図的に、あえて非採用、またはペンディング扱いになっていることがGitHub Issueやカンファレンス動画などからうかがえる。

そして、Dart 1にはそのようなマイナーな改善よりもはるかに優先順位が高い事項があったが、悲しいことに、上記のようにその独自の哲学とビジョンはあまり理解されることはなく、推進力を得ることができなかった。

Dart 2時点では、Dart 1で主張していた動的言語としての側面はなりをひそめてしまったので、Java, Scala, Swift, Kotlinといった言語とのスペック比較がやや正当なものになっていくのではないか。

Flutter

2015年(だったか?)のSkyの発表からそれがFlutterになり、観察、学習しつつもまだアルファ版だからなあと静観していたら、昨年あれよあれよと人気が盛り上がっていき、12月に1.0に到達した。

https://ntaoo.hatenablog.com/entry/2018/12/05/091924

Flutter専門のコミュニティが立ち上げられたり、@_mono さんによる、Flutterの解説を専門とした素晴らしいブログなど、日本でもなかなかの盛り上がりを見せている。

FlutterでDartの魅力を知った方が 初心者にこそオススメしたい言語Dart といった記事を書かれたり、先入観なくDart言語とその開発環境の魅力が評価されていく現象が広がっていってたいへん嬉しく思う。

アプリをFlutter Dartで書けても、ネイティブAPIを叩くプラグインはKotlinかJavaで書くしかないので(AndroidでネイティブAPIを叩くときにDartが利用できるという選択肢は当面ないんじゃないか)両者はうまく棲み分けすることで安定しそうだ。(FlutterでUIを書くFuchsiaも控えていることだし。FuchsiaとAndroidの関係はどうなるのだろう?)

Dartは2.0で良くも悪くも手堅い言語になったと思う。これからは、拡張関数などのマイナーな改善が続けられていく段階に到達したようなので、Kotlinファンの不満もなくなっていくだろう。

Dart for Web

JavaScriptの話題が多彩なのでどうしても見逃されがちだが、Web向けの機能もDart 2に伴って地道に改善が続けられているので、JavaScriptに疲れた人には強くお勧めする。フレームワークについては、当面はAngularDartが第一の選択肢になる。

https://ntaoo.hatenablog.com/entry/2018/12/15/173713

2018年12月にWeb版Flutterとして、Hummingbirdの構想が発表された。たとえFlutter Webが未来だとしても、AngularDartは現在もうある非常に信頼できる技術なので、Flutter Webが安定するまでは第一の選択肢としてありがたく利用させていただくことになる。AngularDartとFlutterでModelのコードを共有しておき、Hummingbirdが安定したらゆっくりと置き換えていけばいいんじゃないかと思う。その期間は、数年はあるのではないだろうか。

サーバーサイドDart

サーバーサイドでも、すでにJavaで巨大なサービスを運用して苦闘している膨大な数のJavaエンジニアにとって、学習コストを抑えて両者を併用して書きつつ、場合によってはスムーズに移行できるという特徴は大きな価値を持つだろう。もっとも、まだライブラリ不足なのは否めないが...

https://aqueduct.io/Jaguarなど、コミュニティベースのフレームワークも年齢を重ねてきている。

Dartが、Webも含めたクライアントサイドのシングルコードベースでの大統一開発環境になろうという構想が現実味を帯びてきたので、この調子で今度はGCPの公式サポートが充実してほしいし、FirebaseやgRPCのDart公式サポートなどその兆しは十分ある。

2018年は、Dartはクライアントサイドにフォーカスすると公式に表明されたが、2019年にはサーバーサイドを含めたユニバーサルな言語となる構想が語られてもおかしくないところまで来ていると思う。

個人としては、Firebase、そしてクライアントもサーバーも普通にDartで書いていこうと思う。ライブラリやプラットフォームのサポートが不足している場合はPythonやGoのお世話になると思うが。GAEとか。

ということで

春にDartのミートアップを予定していて、同志や入門者などDartに興味がある方々と情報交換したいなと思う。 https://dartisans-jp.connpass.com/event/109371/

さまざまな言語を触ってきたが、Dartは、言語仕様が思慮深く選定されつづけ、公式ドキュメントがしっかりしているので迷わず(英語だけど)、ScalaJavaScriptのような先行言語に見られるような、歴史的な経緯による少なくない量のBad Partsによる混沌もなく、JavaScriptのように環境構築やコアとなるライブラリ選定などにもつまづくことがない。Dart 2になっても、Dart 1時代からの、扱いやすくパフォーマンスが高く信頼できる実用言語という特徴は維持している。したがって、本質的なプログラミングに集中できる。

実務的には、自分が技術選定できる場合は、Webでもネイティブでも環境を問わず、当分はDartが第一の選択肢になるかなという感じがする。

2019年は、エンジニアリング方面では普通にDartでアプリやパッケージを書いたりVMコンパイラへの理解を深めていくことに集中するとともに、趣味ではSmalltalkを始めいろいろな環境を触ってさまざまな発想を得たい。このブログも実務的な内容を増やしていこうと思う。

AngularDartの魅力

Dartアドベントカレンダー14日めの記事

Flutter経由でDartの魅力に気づいた人が増えてきているので、ここでAngularDartにもすこしでも興味を持ってもらって、使い始める人が増えると嬉しいと思い、この記事を書いた。

網羅的な解説をする余裕がないので、覚え書きの質で書きちらす。また、具体的な機能の特徴をコードを交えて解説するものではない。それに関しては、https://webdev.dartlang.org/を参照されたい。

なお、よく知られたTypeScript版のAngularとの比較が多くなるため、ここでは便宜的にそれをAngularTSと呼称して、AngularDartと区別する。

Hummingbird (Flutter Web)が発表されたけど

Hummingbird (Flutter Web)が発表されたが、まだオープンソースにもなっておらず評価するには早すぎるので、これからはAngularDartでなくHummingbirdだと決めつけてしまうのは年単位で気が早い。

短期的には、Webで要求されるユースケースのすべてを代替できるかについては懐疑的。将来もしHummingbirdが成功するならば、時期を見定めて数ヶ月から数年かけてゆっくりと置き換えていけばいいのではないか。

それまでは、BLoCパターンなどでModelをDartで共有して、WebをAngularDartで、ネイティブをFlutterで書くというパターンが有効。プラットフォームに依存したコード以外の、Model、ライブラリと開発基盤は再利用できるのが強み。

AngularDartの略歴

一昔前にAngularTSからフォークした。それまではAngularTSをDartコンパイルして提供されていたが、よりDartらしくコードを改善し、パフォーマンスと生産性を高めていく目的。Google AdsのWebクライアントで採用されているのが定番の宣伝文句。その他、Youtubeの一部など、Google内部では活発に使われているらしい。

ちなみにさらに歴史を遡ると、AngularJS (Angular 1)のメジャーバージョンアップの構想ためのアイデアの実装としての側面がAngularDart(初代)にはあり、それがAngularJSにバックポートされつつAngular 2の開発が進められ、そのAngular 2ではTS版もDart版もひとつのコードベースで書かれていたが、なんやかんやあって結局AngularはTypeScript版が推されていき、Dart版はフォークして独自のフレームワークになった。

そして、数ヶ月前のDartのversion2.0のリリースによって、Dartの基盤を利用したAngularDartもversion5.0で成熟したとみなせるようになったので、こうして安心して紹介できる。

AngularTSが、Webのドラフト段階のAPIや将来の構想を先取りした巨大なポリフィルとして冒険するエキサイティングな側面があるのに対し、AngularDartは、巨大なミッションクリティカルなアプリで採用されていることもあり、フレームワークの安定性と生産性とパフォーマンスを非常に重視していると感じられる。

AngularTSがWebコミュニティを積極的に巻き込んでGitHub主体でオープンソースで運営している一方、AngularDartはGoogleの内部リポジトリで揉んで決定されたコードがGitHubに同期されてパブリックになる。ただしGitHub Issueなどで意見を募ったりバグ報告やプルリクエストは受け付けてそれがGoogle内部リポジトリに逆に取り込まれたりといったことがまれにある。

エコシステムやコミュニティの広さはAngularTSのほうがはるかに上である。

なぜAngularDartか

Dartで書ける。つまり、Dartのエコシステムに乗ることで、TypeScript (JavaScript) の欠陥や機能不足、乱雑で混沌としたエコシステムやビルド環境に悩まされずにすむ。JavaScriptをハックする必要がない。

  • ビルドエンジニアを用意したり、Webpackと格闘したりする必要がない。基本的に、package:webdevに用意されているコマンドを叩けばよしなにやってくれる。
  • クロスブラウザ対応のためにJSのAPIのポリフィルを導入したりする必要がない。Dartの豊富なライブラリを利用して開発する。デプロイ時はES5にコンパイルしてくれる。
  • JavaScriptのライブラリを利用したくなる場合は、TypeScriptと同様にJSと通信するコードを用意する。package:jsdart:jsで、d.tsのようなものを書く感覚。ただし、Dartのエコシステムがあるので、そのようなコードを書いてJSのライブラリを利用したくなることはあまりない。

また、Flutterでモバイルアプリを書くならば、Web版はAngularDartで開発してModelを共有しておけば、Flutter WebやDesktopが成功して安定した際はスムーズに移行できるだろう。

主にAngularTSと比較してのAngularDartの特徴

網羅的な比較をする余裕はないので、覚え書き。

AngularDartはAngularTSの知識の大部分を流用できるので、TS版を知っているならば学習コストが少ないし、逆にDart版を知ればTS版も苦労せずに応用できる。ZoneChangeDetection、大部分のテンプレート構文、Dependency Injectionといった、中核となる概念を共有している。

AngularTSは、フルスタックフレームワークとして、JavaScriptの混沌としたエコシステムからの技術選定をせずにすみ、統一した開発環境を提供していることが大きな強みだ。AngularDartでは、Dart言語の基盤がそのような役目を担っており、AngularDart自体はその基盤の上でのWebのUI層を司る位置づけになっている。普段の開発でJavaScriptを意識することはほとんどない。

TypeScriptが、良くも悪くもJavaScriptに型システムを追加しただけの方言にすぎず、JavaScriptの作法やエコシステムへの依存度が大きいことに比べ、Dartは完全に別の言語であり、シンプルで堅牢な言語仕様、ビルド基盤、充実した標準ライブラリ、独自のエコシステムを構築している。Flutterやサーバーサイドなど、Web以外の適用範囲が非常に広い。ただし、そのエコシステム自体はまだ発展途上なのが現実。

テンプレート構文

開発チームはAngularTSからの知識の再利用ができる点については気を払っており、たとえば*ngForもTS版と同じノリで*ngFor="let e of list"と書いたりする。Dartなのに。フォーク前から存在する構文に破壊的変更が入るのをなるべく控える方針のようだ。

ただ、TS版と異なりDart版はテンプレート構文の種類が絞り込まれている。TS版での構文の増加に追随しなかったとも言える。これは良し悪しがあって、テンプレート構文が便利になればなるほど、テンプレートに本来はModelに書くべきロジックが入り込む誘惑が強くなるし、テンプレートのコンパイラの複雑さやIDEサポートの困難が増したり、学習コストが増えたりコードレビューが面倒になったりする。一方、テンプレート構文が便利になるとたしかに簡単さを感じるので、Webコンポーネント的なコンポーネントでhtml(テンプレート)とスクリプト部分が分離している設計では、ある意味仕方のない進化なのかとも思う。

複雑なテンプレート構文と付き合うプログラマーのためにIDEサポートが充実しているのもTS版の特徴で、普通に補完がきいたりリファクタリングサポートがちゃんと動くのもよい。WebStormかVSCodeを使っているとその恩恵を強く感じる。Dart版もテンプレートを解析してサポートしてくれるが、TS版ほど充実しているわけではなく、ここははっきりとTS版に劣るところだ。しかし、そもそもテンプレートの解析が必要なほどのロジックを持ち込むなという良い制約になっているという解釈もできなくはない。 ここは対象ユーザー層の量と質の違いがはっきりと現れているのではないか。

私的には、テンプレートには最低限現状の種類のものがあればよく、増えてもメンテナンスが面倒になるだけだと思う。TS版で同僚がさまざまなテンプレート構文を駆使してテンプレートの複雑度が増していくのをあまり抑止できなかったのが残念ポイントだった。Pipeも一見簡単便利なのだが、本来はModelに書くべきものがViewに露出してしまっていることがほとんどなので、使用を禁止したいくらいだ。

AngularDartは、機能を増やすよりも、パフォーマンスと安定性、生成するJSのコードサイズの削減を重視して、あまり使われていない機能を整理、削除することが多い。GitHubのAngularDartリポジトリCHANGELOGをみたらよく分かる。

AngularCLIは?

AngularTSのCLIは、JavaScriptの面倒な部分をだいぶ見ないですむようにしてくれて、生産性の向上に大きく貢献している。AngularDartではその役割はpackage:webdevが担っており、AngularDartに限らないWeb開発のサポート機能が提供されている。package:webdevの各コマンドを叩けばそれでよく、それ以上のことをする必要がもしあるならば、Dartのビルドシステムを学習してプラグインを書く必要がある。

dart:html

AngularTSのようなマルチプラットフォーム対応のための抽象化層は、フォークのあとに廃止された。現在はdart:htmlをそのまま利用する。パフォーマンスの向上とコンパイル後コードサイズの削減のため。

dart:htmlはDOM APIの一部のおかしな挙動(Node, Elementとか)を修正しているので快適。また、XSSを防ぐためのセキュリティ機能を提供しているので安全である。AngularTSにも似た機能はあったはずだが、AngularDartではフレームワークではなくその基盤のdart:htmlAPIを使う。

SSR

抽象化層を廃した代償として、SSR機能がない。そのため、SSRが必須だと判断されたプロジェクトでは選択肢から外れる。 ただし、2019年の環境で、SSRがそのプロジェクトで本当に必要なのかはよく考えたほうがよい。SSRを維持するためのさまざまなコストを支払えるか、PWA+CDNで問題ないのではなど。

https://speakerdeck.com/kazuyaseki/state-of-seo-for-spa-2018

個人的には、SSRは過渡期の技術とみなしていて、もはや積極的に採用することはない。ほぼアンチパターンだとみなしている。

PWA

AngularDart製のアプリが、Lighthouseのスコアでほぼすべて満点を取れている。 PWAは、Angularフレームワークに同梱はされず、Dartらしくdart:htmlや独自パッケージでサポートしている。

StreamとRx

RxJSの知識を再利用したい、またはBehaviorSubjectを利用したいならば、RxDartを採用する。RxDartは標準ライブラリのStreamを継承して書かれている。

私的な好みでは、Rxの大量のオペレーター群を見るのにうんざりしているので、BehaviorSubject以外は、Dartの標準ライブラリのStreampackage:async, package:stream_transformを組み合わせて使っている、チーム開発ではRxDartを使うのが無難かもしれない。

AngularDartはAngularTSとは異なり、RxStreamも必須ではない。

HttpClient

AngularTSではHttpClientフレームワークの一部扱いだが、AngularDartではpackage:httpを使う。Flutterでも利用できるパッケージ。

Module (NgModule)

AngularTSでは、NgModuleはアーキテクチャ上重大な役目を果たすフルスタックフレームワークの要ともいえるものだが、AngularDartでは、単にProviderを合成してまとめておく薄い機能にすぎず、使わなくとも問題ない。特に困っておらず、逆にTS版ではNgModuleに悩まされることが多かった(個人の経験談)。

Test

package:testpackage:mockitoを使う。成熟している。Flutterでもサーバーサイドでも、どのような環境でも利用できる。

AngularTSでは、型システムを生かしていない設計のJavaScriptテストフレームワークjasmineを使うか、マイナーだがmockitoのTS版を使うかという選択肢がある上に、Angularの外に目を向けるとさまざまなテストフレームワークがそれぞれそれなりに使われている混沌とした状況だが、Dartではパッケージの選定に迷うことはなく、さらにWebでもネイティブアプリでも普通に同じパッケージを利用できる。

スタイルガイド

AngularTSでは、独自のスタイルガイドが細かく規定されてLintが提供され、AngularCLIもそれを前提に作られている。 AngularDartでは、Dartのpackage conventionとスタイルガイドに従う。AngularTSのスタイルガイドはAngularDartでは通用しない。

AngularTSのスタイルガイドは、フルスタックフレームワークとして、コミュニティ内でスタイルに迷いがでないようになる利点は確かにあるが、そのスタイル自体はあまり良いものではないと個人的には思う。

package:angular_components

package:angular_componentsでかなり楽にMaterial Design対応できる。

ほぼ一通りのUIコンポーネントは揃っているが、Flutterほど充実して最新の仕様に追随しているわけではない。

付属のSCSSにMaterial Designのスタイルが一通り定義されているので、その変数を利用してスタイリングしていく。独自にmarginやcolorなどを定義していく必要はない。UIコンポーネントのcolor themeの変更についても、UIコンポーネントごとにSCSSのmixinが提供されているので、それを利用する。

たとえば、'package:angular_components/css/material/material'や、 'package:angular_components/app_layout/layout'をimportする。

個別のUIコンポーネントのスタイルのカスタマイズ用SCSSは、 たとえば'package:angular_components/material_button/mixins';にある。

ちょっと困ったところ

まれに落とし穴に落ちてしまう。

  • UnmodifiableListViewを*ngForに渡すとフリーズする。
  • async pipeにstream transformerをかましたstreamを渡すと、無限ループする。streamを適当にキャッシュして、ChangeDetectionのたびにいちいちstream transformerをかまさないように注意する必要がある。もしくはasync pipeを使わない。

需要があれば詳細と回避方法を書く。

入門のための学習リソース

フィードバック

後半になるほど息切れした。抜け、漏れがたくさんあるかもしれない。

フィードバックをいただけると喜んで回答します。

Resultライブラリを使うべきか

あまり知られていないと思うが、実は、package:asyncにResultライブラリが存在する。

https://pub.dartlang.org/documentation/async/latest/async/Result-class.html

Result

Resultのソースコードに端的に表現されている。やっていることはこれだけ。

  /// Creates a `Result` with the result of calling [computation].
  ///
  /// This generates either a [ValueResult] with the value returned by
  /// calling `computation`, or an [ErrorResult] with an error thrown by
  /// the call.
  factory Result(T computation()) {
    try {
      return new ValueResult<T>(computation());
    } catch (e, s) {
      return new ErrorResult(e, s);
    }
  }

計算結果、または計算過程でのエラーをキャッチしてResultオブジェクトでくるむ。

final result = Result(() => someAction());

あとは、通常の制御構文を使用する。

if (result.isValue) {
  final computationResultValue = result.asValue.value;
  // proceed.
} else {
  result.asError.handle((error) {
    // error handling.
  }); 
}

ExceptionやErrorのハンドリングを網羅しているかどうかを静的に検査できるわけではない。 また、try catchにおけるアンチパターンと同様に、計算を雑にResultでくるんで回復不能なエラーまで握りつぶしてしまわないように注意。

非同期処理をキャッチする

Result.captureでFutureをcatchできる。

final result = await Result.capture(someFutureAction());

Result.captureAllでFutureのIterableを、Result.captureStreamStreamをcaptureできる。

標準のtry - catch - finallyとの使い分け

ほとんどの場合は、標準のtry - catch - finallyを使えばよいだろう。

  • Darttryは優秀で、async awaitを組み合わせれば同期処理と非同期処理が混在していてもcatchしてくれる。
  • プログラマーtryでの処理に慣れているため、学習コストがない。
  • Resultは標準ライブラリに含まれていない。

Resultを使えば、以下のメリットがあるだろう。

  • コードレビュー時に、tryよりもResultのほうがエラーハンドリングの不備が見逃されづらくなる。
  • エラーハンドリングのタイミングを遅延させるほうが便利な、特殊な状況がまれにある。

よって、少なくとも、パッケージを作成するときは、パッケージ利用者にResultの使用を強制しないようにデザインするとよい。つまり、ライブラリの公開インターフェースにResultを使用しない。

プライベートなアプリ開発においては、Resultを使用する基準は、開発チームが議論して決めたら良い。

背景に関してのメモ

Dart言語は、評価が定まっていない仕様を軽率に採用してあとから取り除くこともできず、結果として言語仕様やコアなエコシステムが乱雑になる事態を警戒してきたことが観察できる。たとえ、Kotlin、Swift、ScalaJavaScriptなどの他の言語で良いとされて導入されているアイデアであっても、言語仕様や標準ライブラリへの追加には、極めて慎重で注意深い態度を示してきた。

言語マニアからまれに批判される、パターンマッチングが無いことなどについても、長い議論の末に、極めて意図的に、あえて非採用、またはペンディング扱いになっていることがGitHub Issueやカンファレンス動画などからうかがえる。

そして、Dart 1にはそのような言語仕様や標準ライブラリのマイナーな改善よりもはるかに優先順位が高い事項がたくさんあったが、悲しいことに、その独自の哲学とビジョンはあまり理解されることはなく、推進力を得ることができなかった。

方針変更したDart 2では大きな仕事が一段落したようで、比較的マイナーな事項に関する進化を進めているので、気になる人はdartlang/languageリポジトリwatchしたらいいんじゃないかと思う。

Flutter 1.0 安定版が発表された (Flutter Live キーノート 概要レポート)

本日Londonで行われているFlutter LiveのキーノートがYoutubeライブ配信されていたので、キーノートの概要を手短にレポートする。

ちなみに、Flutterのプロダクトマネージャーがキーノートと同内容を公式ブログに寄稿しているので、英語を苦にしなければ原文を読めば内容は詳細に網羅されている。

Flutter 1.0: Google’s Portable UI Toolkit

Flutterとは

Flutterは、美しいUIのアプリをシングルコードベースでクロスプラットフォーム開発できるUIツールキット。古めの端末でも高パフォーマンスで動作する。Stateful hot reloadingの恩恵により、状態を維持したままコードの変更が即時に開発中のアプリに反映される。(SmalltalkのLive programming環境に少しだけ近づいてきた)。ネイティブ環境に依存する機能はプラグインを開発して呼び出す。それ以外はUIも含めてすべてDart言語で書く。したがって、UIのマークアップ用の外部DSLは存在しない。

すでにGoogle内部で、Google AdsiOS版とAndroid版がFlutterで開発されていることは有名だろう。(ちなみにWeb版はAngularDartで開発されている。)Googleの外でも、特に中国系の企業が積極的にFlutterを使用していて、世界一多くの開発者とユーザーがいるそうだ。

  • 200万人以上のユーザーがFlutter製アプリを利用
  • 25万人以上の開発者がFlutterでアプリを開発
  • 3000以上のアプリがplay storeに公開
  • GitHubの人気リポジトリで34位

f:id:ntaoo:20181205081625p:plain

本日のカンファレンスのキーノートで、ついに1.0のリリースがアナウンスされた。めでたい。

サードパーティのエコシステム

サードパーティのエコシステムについて、3つのサービスが紹介された。

Flutter 1.0の新機能

f:id:ntaoo:20181205084555p:plain

モバイルネイティブアプリから真のクロスプラットフォームアプリ開発環境へ

Flutterでデスクトップアプリ

Windows, macOS, Linux, ChromeOSのネイティブアプリ対応が公式に宣伝された。(現在はまだ安定版ではなくearly preview段階)

https://github.com/google/flutter-desktop-embedding

FlutterでWebアプリ (Hummingbird)

なんと、WebアプリもFlutterでシングルコードベースで書いてしまおうという野心的なプロジェクトが発表された。 これについてはすでにレポートを書いたので参照されたい。

ntaoo.hatenablog.com

Flutterの開発体験デモ

開発中のアプリの状態を維持したまま、リスタートなしにリアルタイムでコードの変更がアプリに反映されている様がうまくデモされているので、開発者は必見のデモになっている。その他、サードパーティのサービスのデモや、Firebase MLKitを使用した、カメラによるアバターとの表情や動作の同期のデモなど。

所感

Dartが、Webも含めたクライアントサイドのシングルコードベースでの大統一開発環境になろうという構想が現実味を帯びてきた。

この調子でGCPの公式サポートが充実してほしいし、FirebaseやgRPCのDart公式サポートなどその兆しは十分ある。2018年は、Dartはクライアントサイドにフォーカスすると公式に表明されたが、2019年にはサーバーサイドを含めたユニバーサルな言語となる構想が語られてもおかしくないところまで来ていると思う。

Hummingbird: FlutterでWebアプリを作る構想

本日のFlutter Liveキーノートでは、Flutter 1.0 GA化を始め、たくさんのエキサイティングな発表がされたが、その中でも一番の驚きは、このキーノートの最後の、FlutterでWebアプリを開発可能にするという、Hummingbirdの発表だろう。

f:id:ntaoo:20181205065537p:plain
Hummingbird

GAの次の構想として、AndroidiOSに加えて、Windows, macOS, ChromeOS, ラズベリーパイなどのネイティブ環境での統一したUIツールキットとなる構想が発表されたことは予想の範囲内だが、Webアプリに関してはWebブラウザーという独特の環境で制約が強いため、Webアプリまでを対象にする構想がこの日に明らかにされたことは全く予想外だった。

これによりFlutterは、モバイルアプリ用途を超えた、モバイルOSでもデスクトップOSでもWebブラウザーでもシングルコードベースで動作する、完全にポータブルなUIツールキットと再定義された。

情報源

英語を苦にしないなら、原文を読むほうが早い。原文を読もう。

Google’s Flutter toolkit goes beyond mobile with Project Hummingbird

Hummingbird: Building Flutter for the Web

Hummingbirdはまだオープンソースにもなっていないので、手元で試すことはできない。したがって、この記事では、主に2つ目の記事である、GoogleエンジニアによるHummingbirdの解説について、その要点を以下に挙げる。

要点

  • FlutterのDartコードをWeb用にコンパイルする必要がある。
  • 特定のプラットフォームに依存したコード以外のサブセットがコンパイル対象。
  • コンパイル先のWebテクノロジーのサブセットを選定する。
  • シングルコードベースでWebでも動作させたい。
  • そのため、ネイティブアプリではC++で提供しているFlutterエンジンを、Web用にHTMLやCSSのサブセット、CanvasといったWebテクノロジーを利用するものに書き直している。
  • すでに、すべてのWidgetおよびWidget FrameworkをJavaScriptに問題なくコンパイル可能。
  • HTML+CSS+Canvasのアプローチに加え、未来に向けてCSS Paint APIの利用も試みている。
  • FlutterからDartライブラリを呼び出すことはもちろん全く問題ない。FlutterからJavaScriptライブラリを呼び出すには、package:jsdart:jsを利用する。
  • Flutterの安定性とパフォーマンスを重視するため、内部ではCSSのわずかなサブセットのみを使用する。CSSを書くことを避け、ネイティブアプリと同じFlutterコードベースでWebでも動作させる。
  • 既存のWebアプリにFlutterの埋め込むソリューションは調査中。iframeとshadow DOMを考えている。
  • Custom Elements, Angular Components, React ComponentsなどをFlutterに埋め込むソリューションについても、調査中。これらの非FlutterコンポーネントがFlutterのパフォーマンスと正確性に問題を発生させる可能性があるため、調査を継続している。
  • Webの制約により、ネイティブアプリのネイティブの機能を利用したコードも含めてすべてをシングルコードベースにすることは不可能(なので、Web用のプラグイン開発が必要)

現在のステータス

  • 大多数のFlutter Galleryを描画できるだけのWeb Engineをすでに開発済み。
  • Cupertino widgetはまだ移植していないが、Material widgetは移植済み。

所感

Flutter for Webについて発表がされ、ネイティブで動作していたデモアプリが今度はWebブラウザーでヌルヌルと動作していることに衝撃を受けたものの、直後に第一感として設計上の疑問がふつふつと湧いてきて、Webの独特の制約や要求事項にどうやって応えるつもりなのか、どういう割り切りをしたのか、どこまで移植可能なのか興味が尽きなかったので、さしあたってこの記事にて上記の通り要点のみ翻訳した。

詳細は訳していないので、気になる人は原文を読んだり機械翻訳を使って理解したらいいと思う。

デスクトップOSでは、ソーシャルアイデンティティなどを大多数のユーザーはWebブラウザーに保持しているだろうから、デスクトップでもネイティブアプリだけでなくWeb PWAも選択肢として考慮する必要があるのは明らかだろう。インストーラブルPWAも進んでいるので、デスクトップネイティブアプリかPWAのどちらを優先するかは、開発するアプリの特性をよく考えて検討したら良いし、うまくいけばほとんどのコードをシングルコードベースにできるので、アプリの開発コストがさらに劇的に下がりそうだ。

また、Webアプリを開発していて個人的にもっとも厄介だと感じている点は、CSSの調整コスト、アニメーション、モバイル用のジェスチャーサポートなので、これをFlutter WidgetとEngineで完全に隠蔽して触らずにすむようにしてくれるだけて、最高のDeveloper Experienceになりそうだ。

WebassemblyでなくJavaScriptコンパイルする点については、まだWebassemblyはまったく成熟していないし、すでに成熟したJavaScriptコンパイラーがあるので、まったく妥当だろうと思う。

AngularDart

Flutter Webが未来だとしても、AngularDartは現在もうある非常に信頼できる技術なので、Flutter Webが安定するまでは第一の選択肢としてありがたく利用させていただくことになる。AngularDartとFlutterでModelのコードを共有しておき、Hummingbirdが安定したらゆっくりと置き換えていけばいいんじゃないかと思う。その期間は、数年はあるのではないだろうか。

Google I/O 2019で続報がありそうなので、ぜひ現地に行きたくなった。行こう。

Flutterでデスクトップアプリを開発

公式ではなくコミュニティベースのソリューションだが、興味深い記事だったので紹介。

https://medium.com/flutter-community/flutter-on-desktop-a-real-competitor-to-electron-4f049ea6b061

以下、上記記事の要点。

  • Androidエミュレーターを立ち上げないので、開発速度は更に高速。
  • デスクトップアプリなので、当然ウインドウは可変。まるでWebブラウザのような感覚のレスポンシブデザイン。もちろんウインドウの枠をドラッグで自由に変更できる。
  • Hot reloadingとdebuggingも従来どおり可能。
  • RAMの使用量に圧倒的な差。エミュレーターは1GB使うのに対し、こちらは100MB。
  • Androidエミュレーターでもデスクトップのウインドウでも動くChatデモの動画あり。
  • ホバーやカーソルの種類の変化など、マウス前提のデスクトップアプリに必要なWidgetを用意。
  • ほとんどの既存のWidgetはすでにデスクトップでも利用可能はユニバーサルなもの。
  • TargetPlatformでページのデザインを分岐。レスポンシブデザインのbreak pointが少なくともモバイルとデスクトップ用で用意されているようなもの。
  • CursorWidgetなどのデスクトップ用Widgetは、モバイルでは単に無視される。
  • デスクトップ用レイアウトとモバイル用レイアウトの切り替えは、PageLayoutWidgetで行う。
  • Pluginは、デスクトップもサポートしているものならばそのまま使える。

さっそく試そうと思ったが、コードは近日中 (soon) に公開されるとのことなので、期待して待ちたい。

Fuchsia OSですでにFlutterが動いているはずだし、デスクトップアプリ開発も公式に対応を研究していたはずなので、アプリはすべてデスクトップも含めてFlutterで書いてしまって、WebはSPA PWAでなくペライチのランディングページで済ますという方向性の開発も予想される。

Webブラウザで動かすFlutter for WebもGooglerの個人プロジェクトベースで模索されていて、WebのUIなどの要求事項は独特なのでなかなかWidgetの再利用は難しいはずだが、すでにあるAngularDartという信頼できるソリューションに加えて、もしFlutterも選択肢に加わると、かなり贅沢なクライアントアプリ開発環境になるだろう。感慨深い。

DartのMixinについての解説 (Dart 2.1対応)

Mixinに馴染みがない人が多いようなので、解説する。

Mixinとは

Mixinとは、fieldやproperty, slotなどと呼ばれている状態 (state)、およびmethodなどと呼ばれている振る舞い (behavior) の集合を定義し、それをclassに適用して拡張するもの。関数スタイルのプログラミングにおける関数の合成のように、classを合成する。

主に、Classベースのオブジェクト指向言語において、(単一)継承の階層という制約では表現することが難しいデザインには、Mixinを適用する。

主な言語における採用事例

言語レベルで採用していたり(Dart, Scala, Ruby等)、あるいはデザインパターンのひとつとして紹介されていたり(JavaScript, Swift等)する。

Mixinの起源

1990年にはMixinについての論文が存在する。 OOPSLA '90, Mixin based inheritance (pdf)

ちなみに上記論文はDart ver1の言語デザイナーによるもの。 あるLisp方言で概念が紹介され、Strongtalkで初めて実装された。

DartにおけるMixin

Dartは言語レベルでMixinを採用しており、Collectionライブラリ、Flutter framework, AngularDartをはじめ、あらゆるライブラリ、フレームワークで利用されている。

Dartでは、すべてのclassは暗黙的に自身のmixinを宣言しており、mixinとして振る舞うことができる。ただし、このシンプルな仕様には残念ながらいくつかの問題があるとみなされ、Dart 2.1において、mixin宣言専用の構文を用意するなどして仕様が改訂された。

入門

Mixinの宣言

mixin 識別子による宣言

Dart 2.1から、Mixin専用の構文が追加された。これからmixinを定義するコードを書くならば、おおむねこの構文で書けば良い。

mixin M {}

class宣言と同様に、mixin宣言のbodyにmemberを書いていく。( memberとは、constructor、 field、およびmethodを指す。ただし、mixin宣言のbodyにはconstructorを書けない制約がある。将来はこの制約は緩和されるかもしれない。 )

mixin M {
  int anIntField = 42;
  String aStringMethod() => "M's method";
}

class 識別子による暗黙的な宣言

従来の、class宣言での暗黙的なmixinの生成も可能。ただし、残念ながらこの仕様は、mixinの仕様書では、将来は非推奨 (@deprecated) とし、廃止するだろうと記されている。

abstract class M {
  int anIntField = 42;
  String aStringMethod() => "M's method";
}

Mixinの適用

with句でsuper classにmixinを適用する。

class C extends Object with M {}
final c = C();
expect(c is M, true);
expect(c is C, true);

expect(c.anIntField, 42);
expect(c.aStringMethod(), "M's method");

なお、Dart 2.1からは、構文上、extends Objectを省略できるようになった。

class C with M {}

また、これは意味上は、

class C extends M {}

と書いても同じである。これはDart 1では頻出のイディオムだったが、class C with M {}と書けるようになったので、今はそう書けば良いだろう。

Mixinの詳細の解説

前述の通り、Mixinは、fieldおよびmethodの集合を定義し、それをclassに適用して拡張するもの。関数スタイルのプログラミングにおける関数の合成のように、classを合成する。

Mixinは、「super classに」適用され、新しい「匿名の」classを生み出す。これを「Mixin Application」と呼ぶ。

MixinのInterface

このMixin Applicationがされた「匿名の」classを継承したclassは、その結果として、自身のmemberに加えてMixin Applicationのmemberのinterfaceを実装している。

class C extends S with M1, M2, M3 {}

上記の例においては、class Cは、super class Sに、mixinであるM1, M2, M3を適用したmixin applicationであるclassを継承している。

final c = C();
expect(c is M1, true);
expect(c is M2, true);
expect(c is M3, true);
expect(c is S, true);
expect(c is C, true);

isは、objectのclassがinterfaceを(直接的、または間接的に)実装する宣言をしているかを検査する。Dartでは、すべてのclassおよびmixinは、暗黙的に自身のinterfaceを宣言している。

Dartのユニークな特徴として、is検査は、objectのclassではなく、あくまでinterfaceを対象にして検査している。たとえば、ある2つのobjectがあり、それらの内部の実装が異なっても、interfaceが同じならば、両者は区別されない (modulo reflection)。

(* Interfaceは、objectのアクセス可能なmethodのシグネチャの集合である。fieldにはカプセル化の規則に応じて暗黙的に対応するgetter method、setter methodが宣言され、そのmethodを通じてfieldにアクセスする。)

Mixinのmember

Mixinのmemberとは、fieldおよびmethodを指す。 classと同様に、mixinのbodyにmemberを書いていく。

mixin M1 {
  int aM1Field = 42;
  String aM1Method() => "M1 method";
}
mixin M2 {
  int aM2Field = 43;
  String aM2Method() => "M2 method";
}
mixin M3 {
  int aM3Field = 44;
  String aM3Method() => "M3 method";
}
class S {
  int aSField = 45;
  String aSMethod() => "S method";
}

class C extends S with M1, M2, M3 {
  int aCField = 46;
  String aCMethod() => "C method";
}

Mixin Applicationを継承したCには、自身のmemberに加えてMixin Applicationのmemberのinterfaceが実装されている。

expect(c.aM1Field, 42);
expect(c.aM2Field, 43);
expect(c.aM3Field, 44);
expect(c.aSField, 45);
expect(c.aCField, 46);
expect(c.aM1Method(), "M1 method");
expect(c.aM2Method(), "M2 method");
expect(c.aM3Method(), "M3 method");
expect(c.aSMethod(), "S method");
expect(c.aCMethod(), "C method");

Memberの探索順序

複数のmixinに同名のmemberがある場合、どのmixinのものを優先するかには明快な規則がある。

  1. まずは、mixin applicationを継承したclassが探索される。
  2. 次に、後から追加したmixinのmemberが優先して探索される。
  3. 最後に、class階層に沿って探索される。

Linerlization (線形化)

たとえば、以下のコードでは、aMethodがそれぞれのbodyに定義されている。

mixin M1 {
  String aMethod() => "M1 method";
}
mixin M2 {
  String aMethod() => "M2 method";
}
mixin M3 {
  String aMethod() => "M3 method";
}
class S {
  String aMethod() => "SS method";
}
class SS extends S {
  String aMethod() => "SS method";
}

そして、Mixin Applicationを継承したCを以下に定義する。

class C extends SS with M1, M2, M3 {
  String aMethod() => "C method";
}

この場合、methodの探索の順序は、C -> M3 -> M2 -> M1 -> SS -> S -> Objectである。

つまり、

final c = C();
expect(c.aMethod(), "C method");

となる。

もし、CaMethod()が定義されていなければ、expect(c.aMethod(), "M3 method");となる。M3に定義されていなければ、expect(c.aMethod(), "M2 method");と、後から追加したmixinのmemberが優先して探索される。その後、SS, S, Objectと、class階層に沿って探索される。

なお、private memberについてもpublic memberと同様にmixinの対象となる。ただし、libraryを超えてmixinする場合は、private memeberにはアクセスできない。

( 注:Dartカプセル化の単位はlibraryである。Private memberは名前にunderscore prefixがついたものである。アクセス修飾子は存在しない。デフォルトでpublic memberとなる。https://www.dartlang.org/guides/language/language-tour#libraries-and-visibility

Mixin Applicationのclassの名付け

Mixin Applicationのclassを、匿名でなく名付けしたい場合は、以下の構文で可能となる。

class CSuper = S with M1, M2, M3;

上記では、CSuperと名付けた。(body ({}) は不要。)

ここで、S, M1, M2, M3の実装は、Linerlizationのセクションのサンプルコードを再利用することとする。

final cSuper = CSuper();
expect(cSuper.aMethod(), "M3 method");

CSuperは、SM1, M2, M3を適用したmixin applicationなので、最後に適用されたM3のmemberが最も優先して探索される。 (規則通り、Sのmemberの優先度は最も低いことに注意。)

さらに、Mixin Applicationのclassに、さらにmixinを追加することも可能。

mixin M4 {
  String aMethod() => "M4 method";
}

class CSuper2 = CSuper with M4;

CSuper2は、CSuperM4を適用したMixin Application classとなる。

final cSuper2 = CSuper2();
expect(cSuper2.aMethod(), "M4 method");

最後に適用されたM4のmemberが最も優先して探索される。

on句とsuper

mixin宣言におけるon句は、super classの制約を宣言する。

  • on句で宣言されたinterfaceを実装したsuper classでのみこのmixinを適用可能となる。
  • mixinのinstance member methodにおいて、super classのsuper invocationを可能にする。(super.foo()).

この制約を満たさないコードはコンパイル時エラーとなる。また、Static analyzerからエラーが報告される。

mixin M on S {
  String aMethod() {
    print(super.aMethod());
    return 'a M Method.';
  }
}

class S {
  String aMethod() => 'a S Method.';
}

class C extends S with M {}
final c = C();
print(c.aMethod());
// a S Method.
// a M Method.

on句は省略可能であり、on Objectと同じ意味となる。

implements句

class宣言と同様に、mixin宣言でもimplements句でinterfaceを宣言し、静的な型検査およびisによる実行時型検査を利用できる。

Generics

classと同様に、mixinにも型パラメーターを追加できる。

現時点での制約

  • mixinにはconstructorを定義できない。この制約は、仕様書によれば将来は緩和されるかもしれない。
  • mixinは別のmixinをextendすることができない。この制約は、仕様書によれば将来は緩和されるかもしれない。

Static member (A.K.A. class member) は、mixinの対象外。

Mixinはあくまでinstance fieldとinstance methodが対象となる。static member (static field, static method) は、classと同様にmixinのmemberとして定義はでき、instance memberからアクセスはできるが、その宣言によって生成されるmixinには含まれない。Mixinでstatic memberを定義することは稀。

リファレンス

仕様書

https://github.com/dart-lang/language/blob/master/accepted/2.1/super-mixins/feature-specification.md

公式サイトでの紹介

https://www.dartlang.org/articles/language/mixins

その他、個人による解説

https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3

まとめ

  • Mixinは、関数の合成のようにclassを合成する。(単一)継承の階層という制約では表現することが難しいデザインには、Mixinを適用する。
  • Dartでは言語レベルでMixinを採用しているため、安心してそれに依存できる。
  • Mixin Applicationのmember探索順序には明快な規則がある。
  • Dart 2.1において、Dart 1において報告されていた問題を解決するために、classから分離して新たにmixin専用のmixin宣言が採用されるなどして、仕様が改訂された。

フィードバックを歓迎します。コメントかtwitterでいただけるとありがたいです。