ntaoo blog

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

視聴メモ : Why JavaScript Programmers Hate You: an ode to dynamic languages, Jan Vitek 

www.youtube.com

2013年のプレゼン。ここでのYouは、このプレゼンの聴衆である言語研究コミュニティを指している。以下視聴メモ。

Why JavaScript Programmers Hate You: an ode to dynamic languages, Jan Vitek

  • 型は生産性を上げるいう伝統的な信念がある。静的型付けは、より良いものだと。目標は正しさ。
  • その伝統的信念に反して、実世界では、動的型付け言語がデザインされ続けてきた。VB, Python, R, Lua, Java (= static + dynamic), JavaScript, PHP, Ruby, Clojure
  • Adobe Lightroomは、大半のコードがLuaで書かれている。簡単に書けるし組み合わせることができるから。スウェーデンで500万人の顧客が利用するペンションの管理システムは32万行のPerlで書かれている。正しさに関しては、彼らは独自のcontract systemを発明した。
  • 実世界で使われている言語は動的型付け言語ということが、統計で示されている。

JavaScriptプログラマーがあなたを嫌うのは、だれも使わない言語を設計するからだ。

  • コンピューティングを必要とする人々の中で、あなたたちはほんの一握りのニッチな集団に過ぎない。あなたの駆使するML, Haskell, Scala, C++は、プログラミングの専門家が大規模なシステムを開発するための、ドメイン特化言語だ。大多数の人々は、それらを使っていない。
  • そのようなDSLおよびその厳格な規律を、他の人々(たとえば、生物学者統計学者など)に押しつけてはいけない。人々にとって、プログラミング言語は、計算論的思考のための導入剤であるべきだ。
  • 静的型付けのベネフィットとコスト。静的型付けは、実行前にエラーを抑止でき、効率的なコードを生成でき、機械的に検査可能なドキュメントとなる。
  • なぜ静的型付けが悪いアイデア?静的型付けは、自明なエラーを潰すことに関してだけは役立つ。しかし、実世界での、Smalltalkという反例がある。静的型付けがなくとも、デバッガーによる高速なイテレーションで自明なエラーを簡単に潰せるので、静的型付けが提供するベネフィットは不要とされる。
  • 静的型付けがより多くのエラーをキャッチできるようになるために、型システムはますます複雑になっていく。それはコンピューター科学者以外のユーザーにとって、大きな参入障壁になってしまう。
  • 静的型付けは、そもそも本質的にモジュール間にまたがったグローバルなシステム。局地的な変更の実験をしたいだけなのに、システム全体で型チェッカーを満足させなければ実行できない。それはコードを硬化させて融通をきかなくさせ、進化の邪魔をして減速させてしまう。
  • 静的型付けが動的型付けと比べて生産性を上げるというデータは乏しい。ある研究では、仮説に反して、両者の生産性に差は見られなかった。
  • 動的型付け言語は手軽に初めることができる。動的型付け言語は静的型付けを強制しないので、プログラミングを初める敷居がとても低い。未完成のコードを実行できる。それが間違ったコードであれ、プログラミングを初めることができる。ほとんどのPHPプログラマーは、言語のマニュアルを一度も読んだことがない。

JavaScriptプログラマーがあなたを嫌うのは、あなたが実世界を無視しているからだ。

  • プログラミング言語のデザインは、実世界での使われ方からの実証に基づくべきだ。
  • JavaScriptのSunspiderベンチマークが想定するコードと、実世界でのコードの傾向はかなり異なる。
  • リフレクションは静的解析を破壊するが、JavaScriptでは常用される。トップ100サイトの82%でevalが使われている。

JavaScriptプログラマーがあなたを嫌うのは、あなたが見当違いの問題を解いているからだ。

  • Java向けのエイリアス解析について言及した13000もの論文PDFがあるが、結局はどのようなJVMにも導入されていない。実世界の、リフレクションかネイティブコードという要素を無視しているからだ。Ownership Types, Information Flow......
  • プログラマーの生産性を第一に考えよう。大事な問題は、どうすればプログラマーが問題解決にかける時間を減らし、正しいコードを生み出せるかだ。
  • 動的言語に異論は多い。動的言語は、プログラムをできる限り実行し続ける。未完成のプログラムを実行し、データタイプを自動変換し、エラーを飲み込む。JavaScriptではエラーはできるだけ遅延される。JavaScriptで実行されるWebサイトは、一部のコードが壊れても、UIの一部が動かなくなるだろうが、サイト自体は動き続ける。もしこれがJava製ならば、サイトがクラッシュするだろう。
  • やりかたが分かるから定理を証明するのではなく、やる必要がある定理を証明しよう。
  • 漸進的型付け、たとえば動的型付け言語であるThron言語では、型注釈を書くことによってプログラムを遅くすることはないし、よくテストされた実行中のプログラムを破壊しない。PHPClojureでの型システムと同じ原理。
  • Rは、もっとも広く使われているlazy functionalな言語。(その事実はだれも知らないだろうが)

結論としては、実際にプログラミング言語の技術は必要とされているが、ユーザーのベネフィットに沿うように、我々の価値体系を変化させる必要がある。言語研究は、実世界での使用方法に基づき、動機づけられなければならない。ダイナミズムを受け入れなさい。そして、静的な解析技術を(JavaScriptPHPなどの)ランタイムに適用しよう。そうして彼らを手助けすれば、我々は彼らに大きな影響を与えることができる。


2013年のプレゼン。JavaScriptの人気については、参入障壁が低い動的言語であることは正解としても、単にWebでのランタイムの選択肢がそれしかないという不幸な環境要因であることが一番大きいだろう。最近はTypeScriptやDartがようやく盛り上がってきているけど。

仮に新たなWebのようなプラットフォームをデザインするとしたら、その言語のデザインについては、やはりシンプルでクリーンな動的型付け言語にして、その上であくまでオプションとしてユースケースに応じての型システム等のツールを提供できるようにして大規模開発にも適用可能なものにするのが良さそうだ。

DartからJavaScriptライブラリのAPIを呼び出す方法についてのメモ

文体をですます調に変えます

DartからJavaScriptライブラリのAPIを呼び出す処理を書くことがまれにあります。まれなのですが、そのたびに書き方を忘れたり罠にはまったりするので、ここにメモを残して思い出しやすくしておきます。

dartdevcでは動作するコードが、dart2jsでは実行時エラーになる問題

https://github.com/dart-lang/sdk/issues/33134

いきなり深刻な問題を取り上げますが、そもそもこの文章を書く気になったのはこの問題に気づいて解決するまでに数時間も費やしてしまったからです。

DartJavaScriptへのコンパイル手法は大別して2つあります。開発時にはdartdevcコンパイラーでデバッグのしやすさを優先したかたちのJavaScriptコンパイルし、デプロイ時にはdart2jsコンパイラーで実行効率を優先したかたちのJavaScriptコンパイルします。特に、dart2jsではさまざまなコンパイラーオプションが用意されています。

JavaScriptの関数は引数の数が合わなくともそのまま受け入れて実行してしまうので、結果としてArity関係のエラーが起きないのですが、dart2jsでは、そのようなJavaScriptの関数呼び出しは実行時エラーにします。一方、dartdevcではそのようなエラーは(おそらく偶発的に)発生しないので、dartdevcとdart2jsではJavaScript API呼び出しのセマンティクスが異なるという問題がおきています。

上記のIssueではこの問題に優先度高で対応することになっているので、そのうち直ると思いますが、開発時に発生せずにデプロイ用コンパイル後に発生する実行時エラーに遭遇すると、かなり精神的なダメージが高いです。

そもそもpackage:jsでちゃんとJavaScript APIの実装どおりのシグニチャを書いて型で守ったら、このようなエラーには悩まされないというのはもっともですが、世の中には実装とAPI Docの記述が乖離しているJavaScriptライブラリがあります。

私が実際にハマってしまった例を挙げると、CライブラリをEcmascriptenしたオープンソースJavaScriptライブラリに対して、そのAPI Docを参照しながらpackage:jsを使用して呼び出し用のDartコードを書いていったのですが、ローカル環境でのdartdevcでは問題なく動作していたので安心していたら、ステージングサーバーで実行時エラーに気づきました。いろいろな実験用コードを書いて問題を絞り込んでいく過程で上記のissueを発見し、JavaScriptコードを読んでいくと、API Docと実際のAPIシグニチャがだいぶ異なるものが複数ある状態に気づきました。

JavaScriptは上記の通りarity mismatchをエラーにせずに、足りない引数にはundefinedを割り当てたり余計な引数は無視して実行するので、一部の引数をAPI Docに載せずに実装と乖離していても、API Docの記載どおりに呼び出せば動作してしまいます。とても怖いですね。

そのJavaScriptコードは、Ecmascriptenしたものなのでかなり読みにくく、とくにコールバック関数のシグニチャを把握するのが困難でした。結局、Ecmascripten元のC APIまで読んでそのコールバック関数のシグニチャを把握し、Ecmascriptenしたほうのシグニチャをそこから推測して、あとはエラーが出なくなるまでシグニチャを書き直して実行を繰り返して解決しました。

そんなこんなで数時間費やしてかなり萎えたのですが、上記Issueを読むと、dart2jsのセマンティクスのほうをJavaScriptのほうに合わせて制限を緩和する方向のようで、たしかに大雑把なJavaScriptの世界に合わせてdart2jsJavaScript関数呼び出し時の型チェックを緩めたら、今回のような苦労をしなくてすむようになりそうです。

それまでは、JavaScript関数呼び出し部分に関しては、dart2jsコンパイルしてテストするなり動作確認をすればデプロイ前に気づくことができます。webdevコマンドでは、以下のように-rまたは--releaseオプションをつければ、dartdevcでなくdart2jsで開発用サーバーが立ち上がります。

webdev serve --auto=restart --launch-in-chrome --debug --release

dart2jsにオプションを渡すには、build.yamlをプロジェクト直下に置いて、たとえば以下のように書けばできます。

targets:
  $default:
    builders:
      build_web_compilers|entrypoint:
        # These are globs for the entrypoints you want to compile.
        generate_for:
        - test/**.browser_test.dart
        - web/**.dart
        options:
          compiler: dart2js
          # List any dart2js specific args here, or omit it.
          dart2js_args:
          - -O1 --verbose --show-package-warnings --dump-info

dart2jsのオプションに関するドキュメントは、https://dart.dev/tools/dart2js です。

https://github.com/dart-lang/sdk/issues/37105 のような関連Issueもあります。Flutter Webのためにも、優先度は高くなっています。

TypeScriptの型定義ファイルからDartJavaScript呼び出しコードを自動生成する

https://github.com/dart-lang/js_facade_gen を使えば、一応d.tsからpackage:js用のDartコードを自動生成できるようです。しかし、更新が3年前で止まっているのであまり期待はしないほうが良いかもしれません。

なお、今回の問題のライブラリでは、d.tsファイルは残念ながら見つかりませんでした。

コールバック関数はallowInterop()でくるむ必要がある

JavaScriptDartクロージャ(つまりメソッドや関数)をコールバック関数として渡すばあいは、そのDartのClosureをallowInterop()でくるむ必要があります。allowInterop()により、それがJavaScriptのClosureに変換され、JavaScript APIのコールバック関数として呼び出し可能になります。私はこの指定をよく忘れてしまいますのでここに記載しました。

https://pub.dev/packages/js

正直、これはpackage:js内部で解決してallowInterop()を書かなくてもすむようにしてほしいです。関連Issueを読むとそのような計画はあるようです。Flutter Webの開発の進捗に合わせて、DartJavaScriptの通信方法についても改善される機運が感じられます。

dart:typed_dataのJavaScriptとの関係

dart:jsのドキュメントによれば、以下の型に関しては、Proxyを噛まさずにJavaScriptと直接の相互利用が可能です。

  • Basic types: null, bool, num, String, DateTime
  • TypedData, including its subclasses like Int32List, but not ByteBuffer
  • When compiling for the web, also: Blob, Event, ImageData, KeyRange, Node, and Window.

https://api.dartlang.org/stable/2.3.1/dart-js/dart-js-library.html

低レベルなJavaScript API呼び出し用ライブラリであるdart:jsのラッパーライブラリとしてpackage:jsがあるという関係性です。dart:jsを使うことは稀で、通常はpackage:jsで事足りるはずです。

dart:typed_dataに、バイナリを扱うための型が定義されています。たとえばUint8ListFloat32Listです。これらはそのままJavaScriptでのUint8ArrayFloat32Arrayになりますので、そのままClosureの引数として渡して問題ありません(ただし、ByteBufferを除く)。

JavaScript PromiseとDart Futureの相互変換

Issueに上がっています。https://github.com/dart-lang/sdk/issues/27315

FirebaseのJSラッパー (package:firebase-dart) は独自で変換するコードを書いているように見えます。それぞれのパッケージで独自でやっているようです。詳しくは調べていません。

その他

Optional parameterとoptional named parameterをpackage:jsにおいて使用する手法など、その他のトピックがいろいろある気がしますが、長くなってきたのでまたつまづいたときに思い出して記事にしようと思います。

Dartでrangeで繰り返し処理

Pythonrangeに相当する関数で繰り返し処理をする。package:quiverを使う。

pub.dev

使用例

import 'package:quiver/iterables.dart';

// 1から10まで出力
for (final i in range(1, 11)) {
  print(i);
}

Smalltalkでいう1 to: 10, Rubyだと1..10

地味にないと困る機能。標準ライブラリに入ってほしいくらいだが、将来、拡張メソッド相当の機能が言語仕様に追加されたら書き方が変わりそう。

Flutter Webの現状調査

Web特有の事情はどう解決するのかに興味があって内部構造などを調べていた。 開発が進むにつれて実装はどんどん進化して問題解決されていくだろうし、現段階のこの情報の正確性も保証しない。個人のメモを公開しているだけなので鵜呑みにはしないようにしてほしい。

あと、Preview版が公開された後にFAQが追加されているので読んでおくほうがいい。

https://github.com/flutter/flutter_web/blob/master/docs/faq.md

ニュース

まとめ

https://medium.com/flutter/a-roundup-of-flutter-news-at-google-i-o-453bb3249981

Flutter Webの開発体験とPreview版段階の技術的制約についての解説

https://medium.com/flutter-nyc/under-the-hood-with-flutter-for-web-bc0d5ce1c11e

Tech Preview

  • 現段階の一時的な措置として、Web用に名前空間を分けている。flutter_web/packages/flutter_webは、flutter/packages/flutter/からコードをコピーされている。flutter_web/lib/io.dartのように、dart.io環境を前提としたコードをwebで動かすための一時対応コードもある。

  • Flutter向けpackageは、たとえネイティブAPIとの通信をしていなくとも、FlutterとFlutter Webのpackage名が分かれているので、Flutter Webではまだ使えない。たとえばpackage:providerはFlutterに依存があるためpackageの依存解決ができない。

  • ChromeSafariは対応済みだがEdgeとFirefoxはテストカバレッジが低いので未対応という状態。

  • 現段階では、さまざまな制約が存在する。上記のNYTimesのゲームアプリの作成を通じたFlutter Webの解説記事から引用。

  • Web-specific code that requires the html package. Keeping separate web/mobile/desktop branches is tricky.
  • No native debugger. While Chrome DevTools is great, working with the IDE debugger is a superior experience.
  • Offline persistent storage that works across web & mobile.
  • Dart isolates are not supported in Flutter for web. Javascript is single-threaded.
  • Text input does not yet match platform conventions.
  • Selecting/copying of text is not yet supported.
  • The browser back button works just like the Android back button, but the forward button is not supported yet.
  • Making Flutter for web more SEO-friendly.
  • Printing with a @media stylesheet isn’t yet supported.

エンジン

Under the hood, Flutter for web draws most elements to the canvas directly except text, which is rendered by the browser

https://medium.com/flutter-nyc/under-the-hood-with-flutter-for-web-bc0d5ce1c11e

すべてのUIをFlutter Widgetで書き、Web用にcompileする。

https://css-tricks.com/the-css-paint-api/

  • flutter_web_ui/lib/src/ui/ <-> pkg/sky_engine/lib/ui/

  • flutter_web/packages/flutter_web_ui が、Webプラットフォームに依存したモジュール。ui/dart:uiのコードがコピーされて、一部にWeb対応のための改造がされている。engine/が、Web版Flutter Engine。ネイティブ版はC++Dartで書かれているもの。Web版はもちろんJavaScriptではなくDartですべて書かれている。

flutter_web_ui/libsrc/engine/dom_renderer.dart DOM操作に使用するサブセットを定義している?

  • flutter/engineへのflutter web用エンジンの統合作業が始まっている。

https://github.com/flutter/engine/pull/8891

レンダリング

  • テキスト以外はほとんどCanvasレンダリングしている。Custom ElementとCanvasのカタマリ。位置はabsolute positionやtransformなどで調整。

  • Chromeのdev toolでelementsタブを見ても構造の把握はほぼできない。代わりに、Dart Dev Toolsを開いてネイティブ開発と同様にFlutter専用の開発ツールを使う。

f:id:ntaoo:20190522151648p:plain

  • CSS Paint APIレンダリングするオプションもあるが、ブラウザーのサポート率がまだ低いので、まだ先の話だろう。

  • UIに関しては完全にFlutter frameworkによってDOM APIが隠蔽されている。AngularのDOM APIを拡張する思想とは対照的。

ジェスチャー

JavaScriptのGestureライブラリを使っているわけではなく、あくまでFlutter frameworkのGestureコード(flutter/lib/gestures.dart)をWeb用にコンパイルするようだ(実装は追っていない)。

Widgets

(プラットフォーム依存コードが含まれていなければ)既存のWidgetをそのまま再利用できる。HTML, CSS, JavaScriptのDOM APIを意識することはなく、完全に隠蔽されている。

ルーティング

Flutter WebはSPAとして動作する。History API操作を抽象化するモジュールを内部で使用している。現在は、AngularDartのルーティング関連モジュールをコピーしている。Flutterのnamed routingでコードを書くと、WebではUrlにpathがつく。paramとquery paramをハンドリングする方法は分からなかった。自分のFlutter力が足りないだけの可能性もある。

  • 最悪、Flutter Web起動時の処理としてdart:htmlを使ってparamとquery paramを取得してFlutter Webに渡せばいけるだろうが、その前にquery paramsがあるとhomeにredirectされてしまう動作を観測している。

  • History push時にquery paramsを指定したい。

  • もしparamsとquery paramsをハンドリングする仕組みがまだないならば、さすがに現段階で実用的なWebアプリを作るには制約が強すぎる。

  • 404ページにする方法もまだ調べていない。

  • 当然、<a></a>tagを書くことはできない。外部リンクを開く方法は調べていない。url_launcherのような仕組みを使うのだろうか。

  • その他、Matrix Paramsのような慣習的な記法をFlutter Webはサポートするのだろうか。

アクセシビリティ

src/engine/semantics/でFlutterのSemanticsARIAコンパイルしている。まだ発展途上のようだ。

ペイロードサイズ

  • Flutter Web Hello World サンプルアプリで146KB zip。これから開発が進むにつれて削減されていくだろう。
  • アプリが大規模になれば、AngularDartのDeferred Loadingのような起動時のペイロードサイズを削減する仕組みがWebアプリでは必要になるが、いまのところそういったものはないようだ。
  • AngularDartと同様、普通のアプリならば全体で300KBから500KBくらいになるはず。

アニメーション

まだカクカクする。改善作業中とのこと。もちろん、CSSやWeb AnimationといったWeb APIを直接操作することはない。

JavaScriptライブラリの利用

  • プラグインシステムはまだ設計段階。
  • 現段階では、dart:htmlpackage:jsを使って直接操作する。

未実装API

一部のAPIは未実装のようだ。たとえばGradient APIが未実装で、実行時にUnimplemented Errorを観測した。

SEO

  • SSRも技術的には可能そうだが、アプリケーションコードをSSR対応にするために条件分岐したりstateの引き継ぎをしたりするのはとてもダルいので、可能でもやりたくないなという感想。
  • DartをJSにコンパイルすると結局EcmaScript 5になるし、Google Search Botは最新版Chromeになることが発表されたので、レンダリングに問題は起きないだろう。とはいえ、レンダリング方法がかなりアグレッシブなので、Google Botからどう見えているのか、実際にGoogle Search Consoleなどで動作テストしてみないと怖い。
  • OGPなどのhtml headに配置するメタデータは当然サーバーでレンダリングしておく必要がある(SSRではなく。ややこしい)。
  • CDNレンダリングなど、状況は変わりそう。

構造化データ

HTMLを書けないので、Schema.orgMicrodataをどうやって指定するのか。HTML埋め込み機能が必要になるのだろうか。

所感

歴史的事情が積み重なって複雑で扱いづらいHTML, CSS, JavaScriptを、Flutter Frameworkが隠蔽して古層にしてくれる。

iOS, AndroidのネイティブアプリをFlutterで開発する際でも、プラットフォームの事情を知らなければハマることもあるので、Webでも同じようにプラットフォームの知識は必要になるが、これまでのようにWeb APIの進化に精通する必要性はだいぶ薄れていくはず。Extensible Webの思想的には喜ばしいことでは。

Flutter Frameworkが隠蔽してくれる代償として、Frameworkの抽象から漏れたWebの機能を利用しづらくなりそう。(たとえば、いま話題のPortalとか。)Web Standardへの対応は進んでいくだろうけど、それまでは生のHTMLやAngularDartなどの従来型のソリューションとの併用が必要になるはず。Flutterがすべてのユースケースに対応するかについてはまだ懐疑的だけど、長期的にはなんとかしてくれそうという期待感はある。

AngularDartは、DOMを拡張する思想のフレームワークなので、Webに特有の要求仕様に対応した詳細な制御が可能。DOMを完全に隠蔽するFlutterとは対照的。詳細な制御ができるが、HTMLやCSSを扱う必要があるので、Flutterに慣れるとそれがとても面倒に感じる。UI以外はAngularDartとFlutterで共有可能なので、AngularDartでWebアプリを書きつつ、Flutter Webの適用可能範囲が広がってくればFlutterでUIの置き換えを検討する戦略。

WebにはAndroidiOSにあるような統一されたデザインガイドラインがあるわけではない。マテリアルデザインiOSデザインの採用を拒否したり理解度が低いプロジェクトではFlutterの高い生産性を発揮しにくそう。現段階では、ペライチ用途やWebサイト用途には向いていない。まずはSPA Webアプリ向け。

Google I/O 19 Dart関連セッションの視聴メモ その2: Pragmatic State Management in Flutter (Google I/O'19)

Pragmatic State Management in Flutter (Google I/O'19)

Flutterで状態管理をする方法についてのセッション。去年も同じペアで同じテーマのセッションがあった。

https://www.youtube.com/watch?v=d_m5csmrf7I&list=PLjxrf2q8roU2no7yROrcQSVtwbYyxAGZV&index=4&t=0s

ひとつのWidget Treeしかないなどの単純なアプリの場合をのぞき、アプリの状態管理は重要な問題となる。

Agenda

  • 状態管理の重要性
  • さまざまな取り組みの推移
  • Flutterでの実践的な状態管理方法

論点

  • 理解しやすく読みやすく保守しやすいこと
  • テストしやすいこと
  • 実行性能(パフォーマンス)が高いこと

残念ながら万能の解決策はないので、あなたのユースケースのなかでエッジケースを考慮するべき。

案1: グローバル変数で状態共有

動くが、ユニットテストも保守も難しい。Widgetが強結合してしまっている。本当に単純なアプリ以外ではするべきではない。

UI = f(state)

  • UIの状態は、アプリケーションの状態を反映したもの
  • UIは他のUIの状態を直接管理しない。
  • なんらかのNotifierで状態をUIに通知する

案2: package:scoped_model

ScopedModel WidgetWidget Treeの先祖(ancestor)に配置し、その子孫(descendant)達に通知する。(現在は、後述のpackage:providerで代替できる。)

案3: BLoC

複雑なアプリ向け。Streamを活用。かなり複雑なアプローチ。

案4: package:provider

package:provideでなくpackage:provider。

package:provideは、ScopedModel version 2とでもいうべきもので、Googleからオープンソース化されたが、同時期にCommunityにより開発されたpackage:providerのほうが優れた選択肢という結論になり、package:providerが公式に推奨された。package:provideは非推奨に。

複雑なアプリを書くためには、

  • Dispose callbackでリソースの後始末をする
  • ChangeNotifierProvider、StreamProviderなどの機能を活用してレンダリングコストを抑える
  • MultiProviderで複数の依存を管理する

Single State Objectを避ける

ただ一つの巨大なState Objectは作りたくない。通常は、精緻な状態管理のためには種別ごとにComponent, Classを分ける。 Providerによってそうすることができる。 (筆者注: これはReduxの一枚岩stateを批判をする文脈ではなく、package:providerで状態を分割管理して更新する文脈)

Performance, Measure

  • Widget Treeの更新範囲をできるだけ小さくする
  • Flutter Driverでパフォーマンス測定してコードの最適化のための根拠にする。(測定なしに当てずっぽうでコードの最適化をしない)

https://medium.com/flutter-io/performance-testing-of-flutter-apps-df7669bb7df7

package:providerが万能、というわけではない

以下のような場合は単純にStateful WidgetsetState()で状態管理すれば十分で、package:providerは過剰設計。

  • とてもシンプルなアプリの場合の状態管理
  • Animation Widgetのような、ひとつのWidgetカプセル化された内部のWidget Treeの状態管理

Testing

Flutterにはheadless test frameworkが同梱されている。ひとつのテストを数ミリ秒で実行できる。

まとめ

  • 状態管理においてScoped Modelはそんなに精巧な手法ではないが、申し分なく良い手法。(筆者注: ここでいうScoped Modelは、Scoped Model version 2であるpackage:provide、そしてそれを非推奨にして代わりに推奨しているpackage:providerを指すと解釈した)

  • 詳細な状態管理が必要になりStreamとRxDartを好む場合は、BLoCパターンが素晴らしい手法。

  • Reduxのパターンに慣れている人には、Flutter向けReduxライブラリが申し分ない手法。しかし、Flutterをこれから始めるならば、package:providerで状態管理を始めることが本当に信頼できる良い選択肢である。

所感

  • 詳細な状態の制御が必要ならばBLoCが最適
  • FlutterでのReduxの採用は、Reduxに慣れていてこだわりがある人以外にはあまり推奨しない
  • いまからFlutterを始めるならば、Providerで状態管理を始めてみてBLoCパターンにも挑戦すると良い

結局はProvider + BLoCに落ち着きそう。最初からProvider + BLoCの決め打ちで状態管理をするのも十分アリだと思う。BLoCの難点は初学者にとってはStreamの学習コストが重いことだと思う。

ProviderはAngularでいう階層型Injectorと解釈できる。昔はDagger2ベースのユニバーサルDIパッケージの開発の構想があったが、実現はせずに代わりにFlutterではProviderを、AngularではAngularのDIの仕組みを使い続けることになるようだ。

UIとModelの通信には、ProviderかBLoCを状況に応じて選択という結論。Model自体の内部の設計についてはこのセッションではなにも語られていないが、主にJavaで培われてよく知られているオブジェクト指向デザインパターンおよびクリーンアーキテクチャ的な設計パターンを適用していけばよく、BLoCパターンについてもデザインパターンへの理解があるならばあとはStreamの操作方法を理解すれば習得できる。

FlutterとMVC

ここで述べられているUI=f(state)は、結局、MVCアーキテクチャを関数スタイルで言い換えたもの。Controllerを通じて状態変更のためのメッセージをModelに送り、状態変更したModelの状態をViewに反映する。PDSしてModel Driven View。WidgetView + Controller

Google I/O 19 Dart関連セッションの視聴メモ : Dart: Productive, Fast, Multi-Platform - Pick 3 (Google I/O'19)

Google I/O 19には数種類Dart関連セッションがあり、Youtubeに公開されている。その視聴メモ。まずはひとつめ。

Dart: Productive, Fast, Multi-Platform - Pick 3 (Google I/O'19)

https://www.youtube.com/watch?v=J5DQRPRBiFI&list=PLjxrf2q8roU2no7yROrcQSVtwbYyxAGZV&index=6&t=0s

Dart入門者向けののSessionだった。

  • Dart 1時代から一貫して、総合的な生産性にフォーカスして言語とライブラリ、エコシステムに投資

  • 3つの側面

    • Productive
    • Fast
    • Multi Platform

Productive

Dart - ライトウェイトなOOP言語で、関数スタイルと静的型付けをサポートした言語。

Dart 2.3。UI-as-code。ソースコードで視覚的にUIの構造を把握しやすくするための進化。Listのなかでif forを使用可能に。Spread operator。UIをより宣言的に記述可能に。

DartVM

  • CFE (Common Frond End)

    • Parser, Lexer
    • -- Dart Kernel: 型推論、型チェック、最適化
    • -- Analyzer : IDE向けの静的解析", "Analysis Server": "解析サーバー
  • Backend

    • JIT Compiler
    • RunTime
    • Debug Service

Hot reloadはDartVM前提

Hot reloadはRuntimeのStateを維持したままコードの変更をRunTimeに反映する。

Flutter CLI -> FE Server (on CFE) -> DartVM (JIT Compiler, Runtime, DebugService)

AoT compile

開発中は生産性向上のためにDartVMで動くが、デプロイ時はコードサイズの最小化、パフォーマンスの最大化のために、AoTコンパイルを行う。ネイティブコードで動作。

Dart for Web

DartはFlutter以前からWeb向けに投資を続けてきた。Google Adsなどの多くのWebアプリがDartで動作している。Google AdsGoogleの最も重要なビジネスのひとつ。何百万行ものDartコードで動いている。

  • 開発中は生産性優先のためにDevCompilerでコンパイル
  • デプロイ時はコードサイズとパフォーマンス優先のためにdart2jsでコンパイル

Web向けHot reloadは安定化にむけ作業中。

Flutter Web

DartVMのサポートが手厚いので、Flutter WebはWeb Browser向けに一部の基盤を差し替えるだけで動作。

f:id:ntaoo:20190511144100p:plain f:id:ntaoo:20190511144125p:plain

Dartの他の様々な動作環境

Non-nullable Types (NNBD)

  • 長い間ペンディングになっていた仕様。
  • Null safety。Runtime ErrorになっていたNull関連エラーをコンパイルエラーにする。
  • この破壊的変更の痛みを最小限にするための移行施策として自動migrationなどを計画中。
  • コンパイル時にnull checkが要らなくなるので、パフォーマンスとコードサイズにも大きな効果がある。

その他言語仕様の進化の計画

  • 新しいConcurrency Primitive (!!)
  • CとC++のコードをより再利用しやすくするための新しいFFI

所感

  • Dart入門者向けにDartとDartVMの魅力をプレゼンしたセッションだった。
  • 宣伝を控えてDart 2にむけた基盤整備に集中するフェーズが終わり、言語仕様の進化に再びフォーカスできるフェーズに入った
  • クライアンサイド開発のブランディングもいいけど、Google Cloud向けサポートもはやく充実させてほしい