ジンジャー研究室

長めのつぶやき。難しいことは書きません。

Elm で個人ホームページを作ってみた

まず個人ホームページって響きが懐かしいな!という話はさておき。

普段は複雑な GUI 作りたいモチベーションで Elm をやっているんだけど、もちろん普通の Web サイトも普通に作れますよということで、参考になれば。

作ったサイト

world-maker.com

高校の頃からやっている自作音楽を公開するサイト。タイトルが中二っぽいけど気にしてはいけない。

ソース

github.com

使った技術

数年前のリニューアルでは Polymer を使ったが、ブラウザのバージョンアップで動かなくなって放置していた(トラウマ)。今回はもっと安心感のある Elm を使う。

MIDI + MP3 プレイヤー

f:id:jinjor:20170511074806p:plain

ピアノロール大好きなので、MP3 と並行して MIDI から読み取ったノートをピアノロールで流せるようにした。描画は Elm 公式サポートの SVG ライブラリ(elm-lang/svg)を使う。普通の HTML 要素と同じく Virtual DOM なので宣言的・富豪的にプログラミングできる。

バイナリデコーダ

MIDI はバイナリ形式だが、現在 Elm はバイナリをサポートしていない。そういう時は JavaScript 側で読んで Elm 側に送り込むのが推奨のなのだが、どうしても Elm でやりたかったので Native(Kernel) モジュールという裏技を使って実現した。もちろんバイナリ用のデコーダーなど用意されていないので、自分で作った。

github.com

Native(Kernel) モジュールを使うのはエコシステムとして健全な方法ではないので広めないでほしい。星は欲しいのでください。

関数型界隈ではお馴染み(?)のパーサーコンビネーターと同じ仕組みで動く。バイナリデータは言語などと違って記述にあいまいさがないので「パーサー」ではなく「デコーダー」としている。これを使うと次のように簡単にかける。

wave : Decoder Wave
wave =
  succeed Wave
    |. symbol "RIFF"
    |= uint32BE
    |. symbol "WAVE"
    |= formatChunk
    |= dataChunk

Web Audio API

現在の Elm は音声周りもサポートしていないので、Portを使って JavaScript 側で処理する。 Port は JavaScript as a Service と考えるのが良くて、クラウド上の画像処理エンジンを使うのと同じ要領で設計すればいい。

今回は MP3 を再生するだけなので大したことはしていない。はまったのは iOS で遭遇する以下の問題。

  1. ユーザーインタラクションを起点にしないと音が出ない
  2. マナーモードにしていると、直に Web Audio API を使うと音が出ない

1 に関しては、有名なハックがあるのでそれを利用。

var unlock = function() {
  var buffer = context.createBuffer(1, 1, 22050);
  var source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start(0);
  window.removeEventListener('touchend', unlock, true);
};
window.addEventListener('touchend', unlock, true);

2 に関しては、以下の記事を参考にした。

WebAudioAPIを使っているはずなのに、マナーモードで音が出る!? - Qiita

HTML Audio、もしくはHTML Videoをページ内で1つでも使用していた場合、 そのページでは WebAudioAPI の音がマナーモード時にも鳴ってしまう

これを逆に利用して、ダミー要素を設置して音が出るようにした。

<audio src="./assets/dummy.mp3"></audio>

Twitter Card

Twitter Card の Player card を利用すると、かなり自由度の高いプレイヤーが Twitter に埋め込めるというのを Picotune@cagpie さんに教えてもらったので、作ってみた。Card Validator を使うのだが、player card の場合は承認されるまでプレビュー出来ない。知らずに何度も試したが徒労だったらしい。

GAE/GO

Twitter Card を利用するためには、 Twitter bot が読むための タグが必要。曲ごとに タグを作らないといけないので静的 HTML 配信だと無理だということに気づく。それまで GitHub Pages での配信を想定したいたのが、急遽 GAE に移動。

サーバーサイドは Go。Node だと Flexible Environment が必要で割高らしい。Java はだるいし、 Python もつまらない。Go はマルチプラットフォーム用にバイナリを吐けるので、習得しておくと何かと便利そう。

Elm アーキテクチャ

The Elm Architecture · An Introduction to Elm

Elm アーキテクチャは Redux の原型みたいなので有名になったのだが、経験知を元に常に進化を続けている。具体的に言うと、昔は「すべては階層型のコンポーネントである」的なノリで作るのは今では無駄に複雑化させるアンチパターンと見なされていて、ビューは積極的に再利用していいけど可能な限りコンポーネントに状態を持たせるのは避けるべしということになっている。

とはいえ、状態の共有が必要なコンポーネントというのは実際には存在して、今回は Twitter Card でホームページと同じ MIDI+MP3 プレイヤーを再利用した。最初はメインにべったり書いていたプレイヤーをモジュールに分離。このリファクタリング機械的に出来る。実際、大掛かりだったわりにバグが一切出ていない。

Platform.program

誤算だったのが、サーバサイドで タグを仕込もうと思ったらコンテンツ情報が Elm で書かれてたこと。何十個か JSON に書き直せばいいのだが、Elm の方が記述量が少なく書けている気もしたので、逆にElmの定義からJSONエンコードするプログラムを書いて(Platform.program) Node から呼び出して JSON ファイルに吐き出した。

普通の CSS

以前に CSS in JS(Elm) を試していて、それはそれで CSS の弱点を消せてよかったのだが、今回は別にそこまで大規模になる予定がなかったので、普通に CSS ファイルを使った。 CSS in Elm だと DevTools からコピペしたのを Elm で書き直す必要があったり、親子関係を見てスタイルを設定できないなどの弱点があった。

あとは、最近になって IE11 以外で CSS Variable が使えるようになった ので使ってみた。便利。

モバイルファースト

スマホは使うけど PC は使わないみたいな人が増えてるので真面目にやってみた。グリッドを駆使したらレスポンシブでモダンなサイトになった。

elm-format

github.com

今や準公式くらいの位置づけのフォーマッター。昔からあったのだが、npm でインストールできるようになった(npm install -g elm-format)のを機に試してみた。

事前に予想していた意図しないフォーマット崩れはほとんど起きなかった(パイプを多用するとネストが深くなる以外)ので、みんな使えばいいと思う。4 スペースインデントもまあ慣れた。

elm-live

github.com

こちらはいわゆるライブリロードのためのツール。Elm 製 Mastodon クライアントの Tooty で使われていたので、試してみた。

操作中の状態を保持したまま CSS だけを入れ替えてくれるのが嬉しい。

感想

Web 技術が色々試せて面白いし、みんなホームページ作ろう。

Elm の update 関数を綺麗に書くための Tips

生活の知恵です。

No more Task.perform identity (Task.succeed Bar)

Bad

update msg model =
  case msg of
    Foo ->
      ( model
      , Task.perform identity (Task.succeed Bar)
      )

非同期にしたせいで2回レンダリングが走る。 Lazy を使えばある程度緩和できるが、できることなら無駄な処理を避けたい。

Good

update msg model =
  case msg of
    Foo ->
      update Bar model

update 関数をそのまま呼び出せばいい。

No more model, model_, model__, model___ ...

Bad

update msg model =
  case msg of
    Foo ->
      let
        (model_, cmd) =
          updateSomething "something" model

        (model__, cmd_) =
          updateAnother "another" model_
      in
        model__ ! [ cmd, cmd_ ]

どの model がなんだったか混乱する(実際これを書きながら間違えた)。

Good

update msg model =
  case msg of
    Foo -> 
      updateSomething "something" model
        |> andThen (updateAnother "another")


andThen f (model, msg) =
  let
    (newModel, newMsg) =
      f model
  in
    newModel ! [ msg, newMsg ]

ヘルパー関数 andThen でスッキリする。

DIV ⇒ SVG 移行して気付いたこと

マップ閲覧・編集システムで画面上に DIV で描いていた図形を SVG に移行した。内部に数百のオブジェクトを含む要素の動作が重く、ドラッグしたときにカクカクになる問題を解決したかった。ちなみに Virtual DOM は使っているがすでに最適化は済んでおり、毎回描画されるような事態はあらかじめ避けている。代替案として Canvas と迷ったが CreateJS のようなライブラリを使わないとイベントハンドリングがきついので、 SVG で行けるならそれが一番。以下、移行した結果と移行中に気付いたことのまとめ。

  • 描画パフォーマンスが上がった(Firefox: 20fps ⇒ 60fps、Chrome: 50fps ⇒ 60fps)。
  • Chrome の DevTools で見ると、DIV 版は UpdateLayout と Paint に時間がかかっているが、 SVG は Layout に時間がかかっている。
  • requestAnimationFrame で測るとデータが飛び飛びだったので平滑化したところ読みやすくなった。
  • Virtual DOM も特に問題なく使える(今回は Elm の SVG ライブラリを使用)。
  • createElementNSsetAttributeNS を使わないとSVG要素として動かないので注意が必要。
  • z-indexがない。あらかじめ並び替えておく必要がある。
  • 隣の要素とボーダーが重なる。普通の DIV だと 1px のボーダーが隣と合わせて 2px になることがあった。
  • <img> の代わりに <image>を使う。 title はあるが alt はない。 SEO 的には微妙かもしれない。
  • 相対位置は <g transform="translate(x,y)"> を使う。xyではない。
  • align: center の代わりに text-anchor: middle と 親要素の中央の X 座標を使う。
  • alignmentBaseline<g> などに設置すると Chrome は効くが Firefox では効かないので <text> に直接つける。
  • Twemoji のような HTML を書き換えて何かするライブラリが動かなくなる。仕方がないのでいったん仮の要素を作って適用してから、SVG に移し替えた。 SVG の中に HTML を埋め込むことはできる(foreignObject要素)ようだが積極活用は避けたい。
  • viewBoxでマップの平行移動、拡大・縮小などができるが、transitionができない(頑張ってanimateで対応しても動作が重い)ので普通に CSS で位置と大きさを指定した方が良い。
  • word-break のような便利なものはない。foreignObject要素でも出来るようだが、今回は<canvas>要素のmeasureTextを使って自力で計算した。

SVG の特性上、やはり文書よりも図形っぽいものに使うのが適していると思う。パフォーマンスは条件によると思うが、少なくとも試す価値はある。Canvas vs SVG は良く見るが、 DIV vs SVG はあまり見なかったので速くなるケースがあることを確認できてよかった。ちなみにデータは以下。計測方法が微妙で発表できる形にまとまっていないので、雑なメモと思って欲しい。元のデータが CSS in JS(Elm) だったので、本当は普通に CSS を使った時と比較した方が良いのだが、その余裕はなかった。

Rendering Performance · Issue #31 · WorksApplications/office-maker · GitHub

複雑な UI を持つ Web アプリの実装課題を洗い出す

Virtual DOM でとりあえずレンダリング周りの問題が解決していて、 Elm を使うと型まわりも解決するのだが、その先に課題が山積してきたので、いま考えていることをメモしておく。前提としては、一般的な GUI っぽいアプリをブラウザ上で動かすことを考えている。お役立ち情報ではないので悪しからず。これで 90 日以上ブログを更新していないとかいう広告も消えるはず。

ルーティング

GUI 延長のブラウザアプリという視点では、アドレスバーは付加機能に過ぎなくて、個人的には「そこは UI じゃないからいじらないで」と言いたいのだが、やっぱり「?q=hogehoge」とかを弄る人がいて面倒だなと思っている。このパラメータ部分は組み合わせ問題で「?a=foo&b=bar」というのがあった時にどうしても「aを指定したときはbは指定するな」等という暗黙の制約があり、その時に単に無視する戦略と URL を修正するのかという問題がある。

それから、 URL をモデルあるいは UI の状態と1対1に紐づけるような設計にしていると思わぬところでハマりまくる。シリアライズ時に、「?a=foo&b=bar」の「bar」の部分だけを更新したいが構わず全部更新してしまったりする。あるいは逆に全部を更新したい場合もある。例えば、何らかのキーをしていしてページを訪れた後に何かを検索したとして、検索は一時的なものだから最初のキーが保存されててほしい場合と、その検索が重要なので新しい URL になっててほしい場合と両方ある。アドレスバーを無視すると、「このページへのリンクをコピー」ボタンというのが良くある UI で、無難な選択肢だと思う。

あとは履歴に残すか残さないか問題。「ページが変わった」と認識されるタイミングで残すのが良いのだと思うが、個人的にはほとんど使わない。というのは、1ページ中に複雑な UI を持つアプリにフォーカスしているので、ページをまたぐなら普通にブラウザ機能で遷移すればいいんじゃないかと思っている。同じ理由で「/foo/bar」という普通の URL をクライアント側でルーティングする気があまりない。サーバーサイドを巻き込むのと、クライアントサイドで複数ページの状態を一括管理するのが面倒なので。

コピペなどの一般的な GUI 機能

コピペや Undo 機能などを持つアプリはブラウザと喧嘩する。テキストフィールドをアンドゥしたつもりが、別の個所も一緒に Undo されたりする。まだ試してないが、編集対象にフォーカスを持たせると干渉しないで済むと思う。

コピー状態は、アプリ内に保持せず何らかの方法でシリアライズしてクリップボードに貼るのが良いと思う。最初はページ内で完結するコピペでも、機能追加で Excel から貼りたいとか、逆に Excel に貼りたいというのが出てくるので、その時に2か所で状態を持っているとどっちか分からなくなり、やっかいなことになる。あとは、クリップボードからペーストする時にセキュリティの都合でただ Ctrl + V をハンドルするだけではダメなので、こちらもフォーカスを解決する必要がある。

Undo については、 Flux の延長みたいなシンプルな方法が高度なアプリでは全然通用しない。モデルを全保存する方法と、アクションを保存して差分適用する方法を両方試したが、どちらにせよ他に考えることがたくさんある。例えば、連続した同じ動作はまとめて戻るとか、ある時刻まで戻るとか。あとは、過去のデータにタイムスタンプを持っていると「戻した結果をセーブする」場合に非常に混乱したので、次回やるときは賢くやりたい。

通信の最適化

画面上でデータをあれこれ編集するようなアプリだと、編集状態をサーバ側に同期したり、他の人が編集した内容をリアルタイムに反映したりという要求が出てきて、通信が大量に発生するので最適化が必要になる。通信量的には、画面全体のデータではなく差分のみを送ると削減できる。通信頻度的には、いわゆるデバウンス(またはスロットル)と呼ばれる仕組みを使う。ただ、やっぱり複雑なケースになるとシンプルなデバウンスでは通用しなくなって、「複数のリクエストを上手くマージしてから送信する」ような仕組みが必要になり、非常に面倒だった。

リアルタイム同時編集については、今のところ楽観的ロックでやっていて、自分の見ているのがいつ時点のデータなのかというのをリクエストに含めて、その間に誰かが編集してたら「駄目だったよん」という風にしている。ダメだった場合にどうするのが適切なのか、悲観的ロックの方が良いのでは?については回答を持っていない。それから、他の人が編集した結果をどの頻度で画面に反映するかも悩みどころ。あまり頻繁でも煩そうだし、通信頻度も増える。

マウスイベント制御

画面上のオブジェクトをクリックしたりドラッグしたりという事を大量にやっていると、処理が干渉してくる。例えば、「クリックした場合は処理A、ドラッグした場合は処理B、ただしドラッグ終了時にはAの処理をしたくない」というのを、 onclick と onmousedown でやっていてぐちゃぐちゃになった。最終的に ClickEmulator のような実装を作る羽目になり、過去2回のイベントからクリックを判定するとか、過去4回のイベントからダブルクリックかどうかを判定するとかをやっていてすごく面倒だった。その辺の解決策が欲しい。

あとは、ブラウザのデフォルトの挙動なのか、なぜか背景にある画像をドラッグするような挙動になることがあり、再現性なく起こるので原因が分かっていない。preventDefault / stopPropagation の組み合わせと Virtual DOM の描画タイミング問題が複雑に絡まっていそうな予感がある。

10000行超のElmを書いて見つけたベストプラクティス

この記事はElm Advent Calendar 2016 の4日目です。

f:id:jinjor:20161204002055p:plain

会社で書かせてもらってるElm製アプリが10000行を超えたので、現時点で個人的にこれはと思うベストプラクティスを実際のソース付きで書いてみる。

github.com

(アプリについての情報は機会があれば)

1.必ずスタイルガイドに従う

行数が増える傾向にあるが、かなり読みやすくなるので絶対に従った方が良い。

Style Guide

関連コミットlet...in中に空行を挿入している)

2.データ構造にタプルを使わない

例えばマウスの位置などをタプルで(Int, Int)のように書きたくなる。しかし後悔するのでやめた方が良い。

-- 微妙
calculateX : Model -> Int
calculateX model =
  let
    (x, y) =
      model.position
  in
    max 0 x
-- 微妙
calculateX : Model -> Int
calculateX model =
  max 0 (Tuple.first model.position)

タプルから値を取り出すにはlet...in中で展開するか、Tuple.firstなどを使うしかない。単純に汚くなるし、firstは値の意味を表していない。代わりにtype alias Position = { x : Int, y : Int }を使う。

-- 良い
calculateX : Model -> Int
calculateX model =
  max 0 model.position.x

このように、明らかにすっきりする。他にも領域を表すのに(Int, Int, Int, Int)などを使っていると、(left, top, right, bottom)なのか(left, top, width, height)なのか分からなくてとても混乱する。代わりに、先ほどのPositiontype alias Size = { width : Int, height : Int }を使うと分かりやすくなる。

タプルは文字通り、複数の結果を「組」にして返したい用途で一時的に使うのが良い。データ構造を表すのに使うと失敗する。

  • 関連コミット (ありとあらゆる場所にあった(Int, Int)の大部分をPositionに置き換えた)

3.型の別名(type alias)を使って可読性を上げる

型注釈がStringだらけになると何を表しているのか分からなって混乱する。

-- 微妙
setPerson : String -> String -> Model -> Model

これを次のようにすると、何をしているのか分かるようになる。

-- 良い
type alias ObjectId = String
type alias PersonId = String

setPerson : ObjectId -> PersonId -> Model -> Model

これに合わせて変数名もただのidからxxxIdに変更したところ、とても読みやすくなった。

4.import時にexposingするのは型のみにする

次のようにimportすると関数がどのモジュール由来だかすぐに分からなくなる。

-- 悪い
import Foo.Bar exposing (..)

exposingするのは型のみにして、関数はモジュール名から書いた方が良い。

-- 良い
import Foo.Bar as Bar exposing (Bar)
  • 関連コミットexposing (..)をやめてモジュール名から関数を書くようにした)

5.一つのモジュールから沢山の型を公開しない

これも同じで、型がどのモジュール由来だかすぐに分からなくなる。Javaほどガチガチにする必要はないが、基本的にモジュール名と同じ名前の型をひとつだけ公開するのが良い。

-- 微妙
import Foo.Bar exposing (Bar, Baz, Pen, Pineapple, Apple)
  • 関連コミットObjectsOperationモジュールにあったDirection型をDirectionモジュールに分離した)

6.パイプを使う

MaybeListを使っていると、どんどんcase...ofが増えて読みづらくなる。

-- 微妙
message =
  case List.head (List.reverse (getPeople model)) of
    Just person ->
      person.name ++ "was the first person."

    Nothing ->
      "No one found."

そこで、パイプが使える。メソッドチェーンのようなものだ。

-- 良い
message =
  getPeople model
    |> List.reverse
    |> List.head
    |> Maybe.map (\person -> person.name ++ "was the first person.")
    |> Maybe.withDefault "No one found."

視線もあっちにいったりこっちに行ったりしなくて良くなる。ただし慣れるまでには少し時間がかかる。早めに慣れよう。

7.CmdよりもTaskベースのAPIを提供する

CmdはTaskより一段抽象度が高く、連鎖させることを許可していない。

Taskであれば、次のように色々連鎖させたリクエストを投げることが出来る。

getUser config
  |> Task.andThen(\user -> getOrganization config user.orgId
  |> Task.map(\org -> { user = user, org = org }))
  |> Task.onError(\err -> Task.succeed (Error err))
  |> Task.perform GotInfo
  • 関連モジュールAPIモジュールはサーバとの通信をすべてTask Http.Error a型で定義している)

8.Subscriptionをケチる

Virtual DOMという富豪的な仕組みを採用している以上、出来ることならMouse.movesのような頻繁に起こるイベントは避けたい。

subscriptionsの型はModel -> Sub msgとなっているので、モデルの状態に応じて蛇口を緩めるようにすればいい。次の例は公式のドラッグ&ドロップの例より。

subscriptions : Model -> Sub Msg
subscriptions model =
  case model.drag of
    Nothing ->
      Sub.none

    Just _ ->
      Sub.batch [ Mouse.moves DragAt, Mouse.ups DragEnd ]
  • 関連コミット (問答無用で垂れ流していたMouse.movesをドラッグ中に限定した)

9.データ構造を真剣に考える

正しいデータ構造(型)を定義することで、堅牢性を上げることが出来る。単純な例だと「必ず1つ以上の値を持つリスト」をList a型で表現することもできるが、これだと何かの拍子に空のリストになる可能性がある。そこで次のようなデータを作る。

type alias NonEmptyList a =
  { head : a
  , tail : List a
  }

これによって、必ずhead(最初の要素)が存在することは保証できる。「何らかの操作をした時にリストの長さが0であってはならない」というテストも不要になる。

もっと具体的・現実的な例は、Richard Feldman氏のトーク「Making Impossible State Impossible(不可能な状態を不可能にする)」で沢山紹介されているので、ぜひ参照してほしい。

  • 関連コミット (レコードをユニオンに直してありえないパターンを撲滅した)
  • 関連コミット (ユニオンをレコードに直してありうるパターンを復元した)

10.カプセル化する

堅牢性に貢献する要素として、データ構造の正しさと同じくらい重要なのがカプセル化だ(これも上のトークで触れられている)。 例えば、先ほどの例では要素が空にならないことは保証できたが、新しい要素を間違えてtailに突っ込んでしまったら全て台無しになってしまう。

そうならないように、Elmではデータへの操作をモジュール内の関数に限定するテクニックがある。

-- 良い
type NonEmptyList a =
  NonEmptyList
    { head : a
    , tail : List a
    }


add : a -> NonEmptyList a -> NonEmptyList a
add a (NonEmptyList list) =
  NonEmptyList
    { head = a
    , tail = list.head :: list.tail
    }

新しいバージョンのNonEmptyListは、モジュール外で直接headtailを触ることを禁止している。代わりに、公開APIとしてadd関数を提供する。 こうすることで、「新しい要素をheadに追加し、それまでheadにあったものをtailの先頭に移動させる」という一連の操作が安全であることを保証できる。

詳細はこちらの記事にも書いている。

11.コンポーネントにすべきかを真剣に考える

Elmにおいてコンポーネントとは「Model / Update / View を一緒に提供する」モジュールを指す。

よくある失敗パターンは、再利用可能で疎結合コンポーネントを作ろうとして本来ひとつであるアプリケーションロジックを分業してしまう事だ。「検索欄はこっちが提供する。ボタンが押されたら具体的な検索ロジックはそっちでやってくれ。結果をコールバックしたらこっちでキャッシュしてあげる。キャッシュをクリアしたいときは命令してくれ。結果の表示はこっちでやる。」もっとシンプルに、入力欄・検索ロジック・結果表示のビューに分ければ良くて、コンポーネントである必要がない。

続いてよくある失敗パターンは、画面を縦横に分割していって、そのままコンポーネントに見立ててしまうことだ。例えば、大抵どんなサイトにもヘッダがある。だから「ヘッダ」コンポーネントを作りたくなる。ところが、ヘッダには色んな機能(ログアウト、設定など)があるから、それらのロジックをどんどん吸い込んでしまい、ページによって内容の過不足が出ておかしなことになる。ヘッダに求められるのは「メニューを渡したらそれを横に並べる」機能で、ヘッダ自体がそれらの機能を持っている必要はない。

すべてはコンポーネント「ではない」。不要な複雑さを持ち込む前に「普通のビューじゃ駄目なのか」をまず検討したい。

  • 関連コミット (「隣にある」という理由だけでフォームと一体になっていたファイル読み込み用のモジュールを切り出した)

Context Menu のデザイン

ブラウザ上に Context Menu を実装するときに何を参考にすればいいのかわからなかったので、自分用にメモ。

見た目

Win10 - Chrome

f:id:jinjor:20161105202640p:plain

Win10 - Firefox

f:id:jinjor:20161105200406p:plain

Win10 - Edge

f:id:jinjor:20161105200412p:plain

Win10 - Desktop

f:id:jinjor:20161105200348p:plain

Mac - Browsers/Desktop

f:id:jinjor:20161105200422p:plain

f:id:jinjor:20161105202508p:plain

Google Spreadsheet

f:id:jinjor:20161105200428p:plain

位置

クリックした位置からメニューが出る方向と、はみ出したときに画面内に収める方法。

環境 デフォルト方向 左右はみ出し 上下はみ出し
Win10 Desktop 左下 シフト 反転
Win10 Chrome 右下 シフト 反転
Win10 Firefox 右下 シフト 反転
Win10 Edge 右下 反転 反転
Mac Desktop/Browsers 右下 反転 シフト
Google Spreadsheet 右下 反転 反転

elm-conf 2016に行ってきたメモ

elm-conf 2016

アメリカ、セントルイスにて。 以下、終わった後に記憶を頼りに書き起こした雑極まりないメモ。

雰囲気

写真は休憩中だから人少ないけど。

f:id:jinjor:20160915122715j:plain

トーク

Keynote: Code is the easy part (by Evan Czaplicki)

コードを書くのは一番簡単な部分。言語設計として何を書くかが問題。 Pythonは最初の5年ほぼプライベートで、ドキュメントが整うのには10年かかった。Elmはまだ4年。 要求を一つずつコードで実装して解決することはしない。数ある要求を集めてパターンを見つける。そのためにたくさんのフィードバックが欲しい。 0.18に向けて今取り組んでるのがデバッガー。モデルの状態を全部記憶できるし、状態を保存してリロードできたりする。あとサーバーサイドレンダリングとか。細々したタスクは今はあえて無視してる。

Beyond Hello World and Todo Lists (by Ossi Hanhinen)

リアルワールド体験談。割と苦労した話とか。 すべてをコンポーネントにしてはいけない。問題を一般化せずに体当たりしていけ。 パッケージの切り分け方とかルーティングとかデバッグとかのTips紹介。

Compilers as Therapists, or Why Elm is Good for ADHD (by Luke Westby)

会場沸いてたけど、ほとんど聞き取れなかったごめん。 ADHDは注意力(集中力)に難のある障害なんだけど、Elmだと大丈夫だった。という話だったと思う。

LT: Rich Animation (by Matthew Griffith)

同時にアニメーションさせるときに割り込むかキューに積むかとか、真面目に考えると結構難しい。 elm-style-animationだとエレガントにできる。

LT: Functional Data Structures (by Tessa Kelly)

バイナリツリーを4種類の方法で実装して比較してみた。 インデックスとかよりもポインタ(union typeとかで)実装した方が明らかにエレガント。

LT: 0-60 in 15 Minutes: Building a Realtime App With Elm and Horizon (by Abadi Kurniawan)

とにかくサーバサイド書きたくない。なら書かなきゃいいじゃん。 Elmを使ってHorizon.js(RethinkDB)のインターフェイスを実装したら、Elmだけでチャットアプリが作れた。

Rolling Random Romans (by Joël Quenneville)

ローマ人の名前の付け方の法則をモデル化してランダム生成してみた。 文脈によって制約が変わったりしてとても複雑。 純粋な関数型でランダム値を生成するにはSeedが必要で面倒だが、0.17でCmdによる生成が可能になった。 ランダムのパターンを自由に組み立てられるのが便利。ボトムアップに組み立てると良い。

Building an Interactive Storytelling Framework in Elm (by Jeff Schomay)

ユーザ選択によって変わるストーリーを生成する。 会場のリクエストで選択肢を決定してストーリーを進めるデモ。最高の盛り上がり。 3パターンくらい試行錯誤した。union type使うと網羅できるけど、書き方が冗長になるのとフレームワークの場合は具体的な内容を知ることができないので万能ではなかった。最終的にリストを使ったDSLに。

The Clockwork Gardener: Growing an Elm App With Templates (by Jessica Kerr)

AtomエディタでElmの良くあるボイラープレートを生成する。(Atomist) 「〜〜を〜〜で〜〜しろ」みたいな一行のコマンドでどんどんElmコードを生成するテンプレート芸。 だいぶ頭おかしい(良い意味で)。 コードの自動生成が怖い?Elmコンパイラがついてるから全然怖くないよ。

Nightingale.space - Elm and Crowd-Source Music-Making (by Murphy Randle)

Twitter上に楽譜を置いたらキューに積んで片っ端から再生していくアプリ。 Elixir(Phoenix) + Elm + JavaScript。 パースに使ってるelm-combine最高(とても同意)。 会場でリアルタイムにツイートされた楽譜を再生していくデモ。楽しい。

Making Impossible States Impossible (by Richard Feldman)

データ構造がしっかりしていればinvalidな状態を作ることは原理的に不可能。 たとえばCSSの@は順番がある。ユーザにリストで書かせるとバリデーションとかテストが必要。 テストはいいけど、しなくて済むならその方がいいよね。 その他の例としてはHistoryとか。 あとは、シングルコンストラクタのパターンを使ってデータをプライベート化した上で、公開したい操作だけを関数で提供する。

トーク全体の感想

とにかく全員トークのクオリティが高くて、どうやったら面白くなるかが真剣に練られてるのがすごいと思った。見習わないと。 英語はほとんど聞き取れなくてスライドの文字情報と雰囲気で補完した。無理。全然無理。

最後のMaking Impossible States Impossibleは、基本的だけどなかなかはっきり言及されてない部分なので、何気に名トークかも。いつも関数型の人が雑に「型があるから安全」って言ってるけど、実は構造体の定義の話もあって「じゃあJavaも型あるじゃん」とか言われてなかなか伝わらない部分。

Q/Aセッション

スピーカー全員が回答者。言語の開発に関してはEvanだったけど。

Q: プロダクションで客にどう説明してる?
A: リスクは確かにあるけど、そこで議論がストップするの良くない。 それに見合うだけあるいはそれを上回るメリットがあるならリスクは取っていい。

Q: Haskell使えない人がコントリビュートするには?
A: Haskellコードいきなりいじる前にコミュニティのコンセンサスとって。あとドキュメントを直接直してくる人も居るけど、いや待ってこれ君の本じゃないから。

Q: デザイナーと協業するには?
A: 前提としてデザイナーは頭がいい。CSSが使えるんだから。 Elmを理解してもらおう。(ツールについての言及もあったけど聞き取れず)

Q: Effect Managerどうなるの?
A: 現時点でほとんど完成してる(Stablish)とは思うけど、今後の展開によっては変えないといけない部分があるかもしれないから安易に公開OKにできない事情がある。

他にもあったけど忘れた。

Evanに話しかけてみた

アイコン見せたらお前かー!みたいな感じだった。elm-time-travelの存在は認知してたけど見てはいなかったみたいで、デモ見せてみたら「これ今まさに作ってるやつじゃん」って言ってた。モデルの出力どうやってるのか聞かれたので、toStringしたやつをパースしてると白状したら爆笑してた。

Evanめっちゃいい人。 唯一知ってる日本語は「仕事がありません」らしい。