ジンジャー研究室

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

関数型言語Elmでオブジェクト指向する

f:id:jinjor:20160420163043p:plain

(4/23 追記:はてブのコメントで指摘をいただいた箇所を直しました。ありがとうございます!)

最近またElmを触り始めているので小ネタを書きます。

このエントリで主張したいこと。

オブジェクト指向とは?

オブジェクト指向と言うと色んな意味を含んでいて、解釈の違いで論争になるのでまずは整理します。だいたい特徴として挙げられるのは以下でしょう。

どのオブジェクト指向に馴染んでいるかは開発者のバックグラウンドによって異なると思いますが、私はオブジェクト指向と言ったら圧倒的に「カプセル化」であって「役割分担」です。というわけで、以降はカプセル化の話です関数型言語オブジェクト指向の代表としては、ElmとJavaScriptを例として取り上げます。Elmに馴染みのない方でも雰囲気は掴めると思います。

関数型言語は構造体に状態を持たないのでは?

カプセル化と言えば、オブジェクトが所持している「状態」を管理するためのもので、関数型言語とは相容れないような気がします。しかし変数(メモリ)の管理方法が違うだけでやっていることは同じです。 例えばUserという構造体の中のnameという値を変更したい場合、JavaScriptでは構造体の中身を書き換えますが、Elmではname以外はそのままに、nameだけ変更した新しい構造体を返します。

// JavaScript
user.name = 'John';// userの中身を書き換える
-- Elm
user' = { user | name = 'John' } -- 新しいuserを返す

新しい構造体を生成していますが、意味的にはそのUserの状態と考えても問題ないはずです。

カプセル化は何が嬉しいんだっけ?

状態へのアクセス方法・変更方法の制限です。構造がむき出しになっているとどんな操作でも可能になってしまいますが、適切なAPIを提供することでそれ以外の操作が起こる可能性をなくしてくれます。

以下は、ゲームで敵にダメージを与える例です。

// JavaScript
enemy.hit(9999);
var dead = enemy.isDead();
-- Elm
enemy' = Enemy.hit 9999 enemy
dead = Enemy.isDead enemy'

このとき、enemyが内部でどんな変数を持っているかを利用者は知らずに済みます。例えばenemyが内部変数hpを持っていたとしても、hpがゼロかマイナスかを気にする必要はありません。isDeadメソッド(関数)を呼び出せば真偽値が返ってきます。また、もしhpがおかしな値になっていたとしても今のところhpを変更できるのはhitメソッド(関数)だけなので、原因をすぐに追及することができます。

重要なことは、カプセル化によってこのような設計を可能にする必要性に関しては、MutableでもImmutableでも関係ないという事です。Immutableにすれば参照を共有することによる予期せぬ副作用を防いでくれますが、どの道このようなカプセル化のアプローチは必要になってきます。

Elmでオブジェクトを作る

先ほどの例をElmでさくっと実装してみます。

module Enemy(Enemy, init, hit, isDead) where

type alias Enemy =
  { hp : Int, mp : Int }

init : Enemy
init =
  { hp = 100, mp = 30 }

hit : Int -> Enemy -> Enemy
hit amount enemy =
  { enemy | hp = enemy.hp - amount }

isDead : Enemy -> Bool
isDead enemy =
  enemy.hp <= 0

ここではなんとなく見慣れたオブジェクト指向と似たコードだ、ということだけで十分です。関数型言語だからと言って肩肘張る必要はありません。同じようにやれば良いのです。

ところで、実はこのEnemy型のhpmpという値には普通にアクセス出来てしまいます。ここからはElmに限ったテクニックですが、次のように書き換えると解決します。

module Enemy(Enemy, init, hit, isDead) where

type Enemy =
  Enemy { hp : Int, mp : Int }

init : Enemy
init =
  Enemy { hp = 100, mp = 30 }

hit : Int -> Enemy -> Enemy
hit amount (Enemy enemy) =
  Enemy { enemy | hp = enemy.hp - amount }

isDead : Enemy -> Bool
isDead (Enemy enemy) =
  enemy.hp <= 0

type Enemy = Enemy { hp : Int, mp : Int }の右辺のEnemyはUnion Type*1のタグです。Maybe型でいうとJustNothingに相当します。そして重要なことは、このEnemyタグはモジュールの外に対して意図的に公開していません。つまり、Maybe型でいうとJustNothingが公開されていないのと同じです。つまりモジュールの外側ではパターンマッチが出来ず、中の値を取り出すことができません{ hp : Int, mp : Int }はプライベートな値なのです。

少しだけコードが煩雑になるので、あまり厳しくなくてよい時は上の方法でやりますが、きちんと管理したい場合は下の方法でより厳しいカプセル化が可能です。

The Elm Architectureにおけるカプセル化

ElmではThe Elm Architecture(以降TEA)と呼ばれる設計でアプリを作るのが一般的になっています。

以下のリンクは、TEAに沿って作られたCounterコンポーネントのコードです。

elm-architecture-tutorial/Counter.elm at master · evancz/elm-architecture-tutorial · GitHub

TEAではコンポーネントから発火したイベント(Action)は必ず一度ツリーの頂点に到達し、アプリケーションの状態を更新します。しかし、コンポーネントから発火したイベントによって影響を受けるのはコンポーネント自身であることが多く、外部にActionの詳細を公開する必要がありません。CounterコンポーネントActionは次のように定義されています。

type Action = Increment | Decrement

このIncrementDecrementという2つのアクションは外部に公開されていません。それぞれCounterコンポーネントview関数で発火し、Counterコンポーネントupdate関数で使われます。

どうモジュール分割するか

TEAでは、アプリケーションの全ての状態を一か所で管理するため、放っておくとどんどんその部分のコードが膨らんでいきます。 そこで、ざっくり方針として

  • 似たフィールドが3つ以上続いたらまとめる

ことにします。例えば、次のようなコードがあったとします。

-- Main.elm

type Action = Select Id | ChangeName String | Ctrl Bool | Shift Bool | Alt Bool

type alias Model = { selected : Id, name : String, ctrl : Bool, shift : Bool, alt : Bool }

update : Action -> Model -> Model
update action model =
  case action of
    Select id -> ...
    ChangeName name -> ...
    Ctrl isDown -> { model | ctrl = isDown }
    Shift isDown -> { model | shift = isDown }
    Alt isDown -> { model | alt = isDown }

この時点で既にActionが5個、Modelのフィールド数も5個で、このまま機能追加していくとパンクすること必至です。しかし、よく見るとなんとなく、ctrl, shift, altという3つのフィールドは一か所にまとめられそうな気がします。そこで、この3つのフィールドと付随するActionをKeys.elmに切り出します。

-- Main.elm

type Action = Select Id | ChangeName String | KeysAction Keys.Action

type alias Model = { selected : Id, name : String, keys : Keys.Model }

update : Action -> Model -> Model
update action model =
  case action of
    Select id -> ...
    ChangeName name -> ...
    KeysAction action -> { model | keys = Keys.update model.keys }
-- Keys.elm

type Action = Ctrl Bool | Shift Bool | Alt Bool

type alias Model = { ctrl : Bool, shift : Bool, alt : Bool }

update : Action -> Model -> Model
update action model =
  case action of
    Ctrl isDown -> { model | ctrl = isDown }
    Shift isDown -> { model | shift = isDown }
    Alt isDown -> { model | alt = isDown }

すっきりしました。キーに関する情報はすべてKeys.elmに押し込めることが出来ています。

機能追加していると、本当にあっという間にコードが膨らんでいってしまうので、モジュール分割は気付いた段階でしておくべきでしょう。Elmは強力な静的型付け言語なので、大規模にリファクタリングしてもほぼバグりません。気付いた時に気軽にリファクタリング出来るのは大きな強みです。

適切な粒度

オブジェクト指向において適切に役割分担するには、経験上、デメテルの法則を意識すると大体上手く行きます。具体的には

  • 2つドットが続くと危ない

を意識します。例えば、ある会社が自社で運用するサービスを開発するとします(コードはJavaScript)。

company.getDev().work();

この例では、会社から開発者を引っ張り出して働かせています。これでは開発物はできるかもしれませんが、サービスが運用されるかはわかりません。すぐに次のコードが必要になります。

company.getDev().work();
company.getOps().work();

このように一連の手続きをセットにする場合、利用者に毎回同じ手続きを踏むように強制するのは困難です。次のようにすべきでしょう。

company.run();

Elmでも同じことができます。もしMainモジュールがあらゆる処理で埋め尽くされていたら、まず切り離すべきは詳細に踏み込みすぎたコードです。

まとめ

ここまで見てきた通り、「データと処理を一緒にする」という発想は関数型言語においても自然と導かれます。また、カプセル化もMutableなオブジェクトの特権ではなく、Immutableなオブジェクトでもモジュールが処理を制約できれば普通にできます。

このエントリの狙いは、オブジェクト指向言語から関数型言語に移行する際のある種の懸念を払拭することです。お役に立てれば幸いです。

*1:またはtagged union, ADT

Markdown形式で書いた記事やレポートをPDF形式で配布する

あまりまとまってる記事がないので書いた。

やりたいこと

Markdownでさらっと書いた記事を社内に共有したい。 しかしMarkdownだと色々問題がある。

  • 標準的なビュアがない
  • PC環境によって見え方が変わる
  • 画像などを含めるためにZIPなどで共有する必要があり読む側が面倒

Gistでもいいが、GistだとprivateにしてもURLがバレたら普通に外から見えるので実質public。

というわけで、PDF1ファイルにまとめたい。 スタイルを含んだHTML1ファイルでも良さそうだが、フォントがOSによって変わったり、Webフォントだとしてもオフライン時に崩れる問題があるので、やはりPDFが理想。

Markdownの下準備

PDFに変換する前にやっておくと良いかもしれないこと。

目次の自動生成

ある程度長い場合は目次が作りたくなる。 Node.js環境ならdoctocというツールが使える。

改ページの制御

PDF出力または印刷時にどうしても改ページしたい場所があれば、Markdown中に次のコードを入れる。

<div style="page-break-before:always"></div>

あるいはclass指定してCSSで制御しても良い。

PandocによるHTMLの出力

Pandocを使うとMarkdownからHTMLやPDFが作れる。 ただし日本語の場合はPDFを作るのにTeXが必要。そのためだけにTeXをインストールしたくないので、HTMLを出力してChromeの印刷機能でPDFを出力することにする。

シンタックスハイライトを有効にする

Pandocのオプションで有効にする言語を指定。複数の場合はカンマ区切り。

--indented-code-classes="haskell,css"

Markdownで次のように言語を指定します。

 ```haskell
 qsort [] = []
 ```

CSSをカスタマイズする

Pandocで何も指定しないとスタイルが微妙だったりするので、CSSを指定するようにする。ちなみにHTMLには埋め込まれない模様。

pandoc article.md -s -o output.html -c style.css

GitHub風にしたい場合はgithub.cssが使える。あとはBootstrapベースで作られたこちらのCSSもなかなか良い。

必要に合わせてfont-familyなどを変更する。以下は追記するCSSの例。

body {
  padding: 10px 10%;
  font-family: 'Noto Sans Japanese', "Hiragino Kaku Gothic Pro", meiryo, "Open Sans", sans-serif;
}
table {
  width: 100% !important;
}
col:first-child {
  width: 2%;
}
img {
  max-width: 100%;
}

画像幅をカスタマイズする

Markdownでは画像の大きさが指定できないので、CSSで一つずつ書く。

img[src="foo.png"] {
  width: 75%;
}

特定のテーブルのスタイルをカスタマイズする

CSSで指定するために本当はテーブルにIDを振りたいが、そういう機能がないので隣接セレクタでなんとかする。

<div id="table-foo"></div>
|名前|値段|
|:--|:--|
|みかん|50円|
|りんご|100円|
#table-foo + table {
  width: 30% !important;
}

Webフォントを使う

Windowsだとフォントが微妙だったりするので、Webフォントを使う。上のCSSで指定している'Noto Sans Japanese'はHTMLに次のコードを加えると使えるようになる。

<style>
  @import url(http://fonts.googleapis.com/earlyaccess/notosansjapanese.css);
</style>

HTMLに直接書くと次の更新時に上書きされてしまうので、HTMLのヘッダにコードを含めるオプションを利用する。

-H import-font.css

ChromeによるPDF出力

Chromeブラウザで印刷画面を出して(Ctrl+P)PDFで保存。他のブラウザは使っていないので知らない。

f:id:jinjor:20160204151733p:plain

以上。あとはこの一連のプロセスをスクリプト化したら快適になりそう。

TOEICのリスニングCDを分割するWebアプリを作った

TOEICのリスニング問題集をやっていて「ムキーッ!」となることありませんか?

私は2つほどあります。1つは「ひとつの問題を繰り返して聞きたいのにファイルが分かれていない」こと、もう1つは「何を言ってるのかさっぱり分からない」ことです。そこで今回、1つめの問題を解決すべく、CD音源を複数の問題別に分割するWebアプリを作りました。

Wave Cutter for TOEIC®Source

f:id:jinjor:20151228012935p:plain

ChromeFirefox、Edgeで動作確認済みですので、ぜひ遊んでみてください。

使い方

  1. MP3ファイルを読ませると自動的に空白を判断して分割します
  2. 自動分割で上手くいかなかったところを手動で調整します
  3. 完了ボタンを押すとZIPファイルが手に入ります

2に関しては、出力予定のファイル名(左側)と波形データの内容(右側)を一致させるゲームだと思うと手っ取り早いです。

主な機能

  • 波形の削除、分割、結合、再生
  • 分割後のファイル名の付け方の指定
  • Undo/Redo
  • 自動保存

技術的な解説

大掛かりなフレームワークに飽きてきたのでミニマルな感じで攻めてみました。

Virtual DOM

Virtual DOM実装としてSnabbdomを使いました。理由は以下です。

  • 軽い: コアが200行程度
  • 簡潔な記法: h('div#foo.bar.baz')のようにNodeがさくさく書ける
  • Hook機能: パッチを充てる前後などに処理を書ける

Hook機能は、canvas要素のようなVirtual DOM的な思想から外れるものを扱うときに便利です。 今回は音声処理という重い処理を扱うので、Modelが変更されていなければrenderXXX()を走らせないということもしています。

それから、requestAnimationFrame()を使ってレンダリングの頻度を抑えています。 これはModelからViewを生成する関数が純粋であることが前提です。

簡易Flux

ライブラリなしで簡単にFluxしました。

Actionを溜めておくことでUndo/Redoへの対応が楽になりました。最初の状態とActionのリストさえ覚えておけば任意の状態を再計算できます。ただし全てのActionを溜めてしまうと、hoverやらtickみたいな頻度の高いActionに汚染されてしまうので最小限に。言い換えると、Undo/Redoを完全にフレームワーク任せにすることはできません。

今回はModel(Store)をObserverにする必要は無いのでカット。というより、ModelからViewへの紐づけをObserverでやるのはBackbone.jsでカオスになった事があって懲りています。

File API

読み込みと書き込みに使用。

Web Audio API

音声処理に必須です。

Web Workers

編集後、MP3に再エンコードする時に画面がフリーズしたので急遽導入。

用途をTOEICに限定するメリット

無駄に汎用的に作りたくなる気持ちを封じることでメリットを出します。

  • 空白時間はおよそ決まっているので、ユーザーがしきい値などを設定する必要がない。
  • 波形の分割ポイントを「空白の最後(次の波形の直前)」に限定できるので、分割ポイントを選択しやすい。
  • ファイル名の付け方のパターンが決め打ちできる。例えば、Part3からは41-43.pm3などの名前が嬉しいと分かっている。

まとめ

最新のWeb技術を使って役立ちそうなものを作ることができました。残タスクは以下です。

  • MP3エンコードの高速化
  • メモリ不足対策
  • CDによる差異を埋めるために分割ロジックを賢くする
  • UIを洗練させる
  • リスニングを克服する

以上。

OSS関係で英語を書くときに心がけていること

f:id:jinjor:20150828091646p:plain

最近、OSS関係でGitHubとかMLとかに顔を出していて、当然ながら会話は全部英語。

というわけで、英語を書くときに心がけていることを簡単に書く。

「英語が下手ですいません」とか前置きしない

読めば下手だって分かるから、わざわざ言う必要ない。これ言ってる人を見かけるとほぼ確実に日本人なんだけど、必要以上に卑屈なオーラを感じるので良くないと思っている。いくら日本人が英語苦手とは言え、英語圏の人は糞な英語に慣れてるから大体分かってくれるし、分からない場合はこういう意味かとレスが来るから、その都度説明すればいい。ただし後にも書くように礼儀は必要なので、甘え切って雑になるのはよろしくない。逆に丁寧に書けば懸命さが伝わり好印象

あと、日本人以外にも非ネイティブは沢山居ると思うと結構気が楽。自分の感覚としては非ネイティブの書く英語ほど分かりやすい気がしていて、ネイティブの方が表現が小洒落てて時として全く分からない。

相手に通じる事が第一

自分も文法とかめっちゃ気になっちゃうんだけど、格好つけて洒落た表現にした結果通じないとかもう本末転倒なので、次のように心がける。

相手に通じる >>>>>> 文法の正しさ、簡潔な表現、etc.

一文が長くなって接続詞やら関係代名詞とかゴロゴロしてきたらブツ切りにして複数の文にしていいし、短い表現で分かりにくければとことん具体例を書いて説明する。 あとは日本語でも良くやっちゃうけど、「~~みたいな時に悩むっていうか。どうしようか。」みたいな曖昧な訊き方は避ける。お前は何で困っててどうしたいんだ!?ってなるので、ちゃんと説明したほうがいい。ネイティブ同士でもここを疎かにしていると普通に会話が通じていなかったりする。裏を返せば、自分が理解できないのは英語力のせいとは限らない

積極的かつ無礼でないというバランス

慣れてくると今度はラフにあれこれ発言し始めたりするんだけど、最低限の礼儀なりルールみたいなのは普通にみんな守っているので、そこは外さないようにする。例えば、Issue立てる前にまず既存のIssue検索しろよとか。その辺はもう言語が英語だろうがなんだろうが関係ないので、調子に乗って羽目を外さないようにする。

よく日本人は空気読むとかおもてなしがどうのとか言うので、なんか海外の人は基本ラフで言いたいことは気にせずガンガン言っちゃうイメージあるけど、なんやかんや万国共通だなと思うことはある。「あ、これ本当は言い辛いんだけど角が立たないようにこういう言い回ししてるんだろうなぁ」みたいな事もよくある。

ノリは周りに合わせる

普段英語を使わないので、自分で書いている文章のニュアンスが分からなかったりする。特に、敬語、丁寧語、口語、俗語あたりは本当に分からない。通じればいいと書いたばかりなんだけど、それでもなんとなく「いやマジで俺に言わせればこうすべきだと思います」みたいな風になりたくないので、適当に周りに合わせている。YesなのかYeahなのか、canなのかcouldなのか、Iなのかweなのか。あまり俗っぽい言い回しは理解できても自分で言うのは避けるとか。あと、顔文字も便利。

少しずつ表現を覚える

ここまで書いたのは、とりあえず今の知識でなんとかするっていう話なんだけど、やっぱり限界が来るので少しずつレパートリーを増やすことにしている。例えば、最近覚えたので言うと、意見として「よく分からない」という場合には「I don't know」だと投げやり感があるので「I'm not sure」がベターとか。

こういうのって受験英語で覚えた記憶がない。本当は教科書でガリガリ覚えたいんだけど、正直、2つ目の角を右に曲がって八百屋の正面の郵便局に行くとか、注文した商品が届かなかったから何時から何時の間に電話しなおすとか、遠い世界のような気がしてしまうので半分諦めている。

翻訳ツールとか

読む時に使っているのはGoogle翻訳プラグイン。選択したら訳してくれるので便利。他にも似たのがあるけど、一番シンプルで気に入っている。文単位で訳すことは多分ない…というか、文単位で分からないやつはたいてい翻訳機も誤訳するので。

書く時に分からない単語は、普通にググるWeblioが大体トップに出てくるのでそれを使っている。ただどうしても複数の候補が出てしまうので、例文を読んで一番近そうなものを選ぶ。それでもしっくり来ないときは別の表現を考える。

最後に

色々言ってるけど、間違えたときに正してくれる人が居ないので、普通に間違え続けている可能性が大いにある。でもそういう人を探すのは別のコミュ力が要るのでハードルが高い。

cabal install/build 時に実行時に参照するファイルを含める方法

やりたいこと

いまいち上手く日本語に出来なかったので図解する。

f:id:jinjor:20150817141116p:plain

コマンドラインツール等で、実行時に手元のファイルをテンプレートとして利用したり、静的ファイルをディレクトリごとコピったりしたいことが良くある。でもそのままExecutableにするとファイルが付いてこなくてどうしよう、と言う話。上の図で言うと、赤い矢印で示したファイル参照を実現したい。

.cabalファイルの記述

実行時に必要なファイルを.cabalファイルに記述する。Data-dir:に必要なディレクトリ、Data-files:にそのディレクトリ下のファイルを羅列する。この指定が曲者で拡張子ワイルドカードに出来ない。なので、/**/*とかにしたいのを我慢しつつ、拡張子をひとつずつ記述する。

foobar.cabal

Data-dir:
  data

Data-files:
  templates/*.html.tmpl
  templates/*.js.tmpl
  assets/*.svg
  assets/*.ico
  assets/*.png

Haskellから呼び出す

foobarというパッケージに付随するファイルは、Paths_foobar.getDataFileName :: FilePath -> IO FilePathで呼び出せるようにcabalがコンパイルしてくれる。魔法か。

Main.hs

import Paths_foobar

main :: IO ()
main =
  do
    path <- Paths_foobar.getDataFileName "templates/app.js.tmpl"
    putStrLn path -- 絶対パス

どうなっているのか

手元のWindowsで、インストール先はこんな感じになってた。

C:\Users\UserName\AppData\Roaming\cabal
 ├ bin
 │  └ foobar.exe
 └ x86_64-windows-ghc-7.10.2
     └ foobar
       ├ templates
       └ assets

応用例:インストール時にコンパイルしたファイルを使う

データディレクトリに入れるファイルをインストール時に動的に作りたい場合がある。例えば、配布しているソースは.coffeeで、実際に使うのはコンパイルした.jsという場合。

.cabalファイルで、Build-type: Customとすると、Setup.hsファイルにビルド時に呼ばれるフックを記述できる。内容は略。

foobar.cabal

Build-type: Custom

Setup.hs

(略)

作ったもの

Elmでパッケージを公開する前にドキュメントをプレビューできるツール

github.com

参考用にどうぞ。

スケーラブルなプログラミングのために何が必要か

f:id:jinjor:20150516071422p:plain

Fluxに関する独自解釈と妄想を、何かの翻訳っぽく書いた。

スケールするアーキテクチャ

フレームワークを作る時、我々は「簡単に記述する」ことを第一に考えがちだ。 そして、簡単にするための仕組みはウケる。 逆に記述量が増えるとウケない。

しかし例外があって、多く書くことによるメリットが受け入れられたときは別だ。 例えば、Backbone.jsを使うと記述量が増える事は誰もが認めるところだが、MVCの実現というメリットのために広く受け入れられた。 要するにトレードオフなのだ。

ここのところFluxアーキテクチャが注目を浴びているが、書いてみると記述量は相当増える。 そもそも登場人物が多すぎる。 Action、Dispatcher、Store、View、それからそれらの間に挟まって仕事をする者達。 一体彼らは何をしたいのか。

最近になって分かってきた。 これはアプリケーションそのものを抽象化したアーキテクチャなのだと。

Fluxは何がしたいのか

FluxはMVCを置き換えるものとして登場した。 本当にそうだろうか。

Actionは必要か

例えば、AngularJSやBackbone.jsはActionを必要とせず、そのままモデルを更新する。 あるいはビューが直接モデルを更新するのは責務上よろしくないとか、ロジックの共通化という目的で、例えばコントローラに関数を置いたりする。 しかしActionは登場しない。 むしろ直接関数を呼び出す方が手軽なのでは?

結論から言うとActionは必要だ。

全ての出来事はデータだ

データベースを普通に使うと大抵は最後に反映された状態のみを保存しているため、それがどういう経緯で作られた状態なのか復元できない。 しかし、全てのイベントを保存しておけば、過去から現在の全ての状態が再現できるという考え方がある。 そうは言っても無限のリソースを持っているわけではないので、どこかで情報を圧縮することにはなるのだが、確実に言えることはイベントの集積が情報として一番強いということだ。 そして関数を適用するにしたがって、計算結果を得る代わりに次第に情報量は減っていく。

こうしておけばDBに専用のフラッシュバック機能は必要ない。

出来事はそれがどのような用途に使われるかを知らない

「いつ誰がどこで何をした」という出来事は、特定の用途には情報過多かもしれない。 しかし安易にそれを捨ててはいけない。 後からアドホックに追加された機能によって、別の可能性を見出されるからだ。

ある機能は「何をしたか」によって処理を切り替えるかもしれない。 しかし後から追加された機能が「どこでそれが行われたか」でフィルタリングして分析を開始するかもしれない。

順番に届いた出来事を好きな人が好きなだけ持っていく

Actionを実際にDBに保存するかはさておき、何らかの手段でそれらはキューの形で運ばれてくる。

同じ情報を使う機能がいくつもあるということになれば、Pub/Subモデルが適している。 そういうわけでDispatcherが登場する。 DispatcherはただPub/Subの仕組みを提供するだけで、具体的にどんなSubscriberがいるかには無関心だ。

スケーラビリティは不可逆

このように順序立てて考えていくと、Fluxが見えてくる。

ここで重要なのは、このアーキテクチャ最も理想的なアプリケーションの構造を抽象化したものだということだ。 実際に必要か否かに関わらずActionは存在するものだ。だからそれを表現した。

そうなると書きやすさは二の次だ。 おそらく目の前の書きやすさにフォーカスするのなら、必要なのはFluxではなくAngularJSだ。 Fluxはもっと大規模を想定する。

最近よく考えるのは「スケーラビリティは不可逆」だということだ。

サーバー台数を横に増やす話ではなく、ここで言っているスケーラビリティとはなるべく同じコードを保ったままアプリケーションの構成を変えられるという意味だ。

マイクロサービスが話題になっているが、何も考えずにモノリシックに書き始めるとマイクロサービスに拡張することは永久に不可能だ。 そこで、じゃあ最初から考えろよという話に普通はなるのだが、ここで追求する理想は、考えなくても拡張できる状態になっていることだ。 言い換えれば、小さいインフラでも大きなサービスと同じ書き方をしておいてスケーラビリティを確保せよという話だ。 小さいうちから大げさに?いや、大げさであると思わせないほど簡潔に表現するのだ。

そのためには、何かしらの言語なりフレームワークが必要だ。 Fluxが出てきた時に確かに「MVCはスケールしない」と言っていたのだが、MVCのスパゲッティ状態を解消する目的という話に発散してしまったようだ。 それで、なるほどAltMVCかと思って考え始めたのだが、どう考えてもActionが必要なかった。

しかし、スケーラビリティに注目すると色々と辻褄が合う。 そうなると全然フロントエンドだけの話ではない。 全てを支配できる。

以降、追加でスケーラビリティ実現に何が必要かを考えてみる。

本質的でない状態を排除する

主にサーバサイドにおいてスケーラブルなアーキテクチャを指向してアプリケーションを書き始めると、ひとつの気付きがある。

状態を管理する必要が全くないという事だ。

昔からステートレスにしなさいとは言われていたが、Amazonに至っては思い切ってLambdaと表現するなどしている。

言ってしまえば、アプリケーションとはアクションと古い状態を入力として新しい状態を返す関数ということになる。 型をつけるとこうなる。ちなみにモナドではない。

application :: Action -> State -> State

ここでいう状態というのは例えばDBなどの事を指していて、決して計算途中の値のことではない。 forループの外にあるsum変数などは本質的な状態ではない。あるいは設計の都合上たらいまわしにして構築されるオブジェクト、これも状態を持つ必要はない。 言い換えれば全て純粋な関数で書けるということだ。

関数型言語が必要

しかし我々は「慣れていて書きやすい」という理由で不用意に状態を扱ってしまう。

例えば先ほどの関数で、新しい状態を返す代わりに古い状態を書き換えたらどうなるだろうか。 関数の呼び出し側は新旧の値の比較が出来なくなってしまう。 実際、このことがReact.jsの最適化を妨げる要因となっていて、Immutableを売りにした類似フレームワークは軒並みパフォーマンスが高い。

他にも有名な例としては、リストから新しいリストを作るときにmap関数を使うかforループを使うか、というものがある。

val newList = oldList.map(_ + 1)

簡潔に書けているが、問題はそこではない。 重要なのは、既に並列計算のための準備が出来ているということだ。

val newList = oldList.par.map(_ + 1)

for文でこうはいかない。 手続き型言語では、知らず知らずのうちにスケーラビリティを落としているケースがあるのだ。

何故か。 状態を変更する方法だけを提供すべき関数がそれを実際に適用してしまったり、各リスト要素の変換方法だけを提供すべき関数がリストの作り方にまで言及しているからだ。 こういう事が平気で起きてしまうのは、純粋関数型言語以外は副作用の有無を区別しないからだ。

しかもだんだん規模が大きくなってくると、どこでそういうことが行われているかが全く分からなくなる。 そしていざという時になってHadoopへの移行は無理だね、という話になる。

デフォルト非同期

また少し違う観点で話をすると、Node.jsのような非同期ベースは最早当たり前にあって良い。

Node.jsで現状不満なのは、非同期のほうがコード量が増えるということと、非同期APIと同期APIが全く別の書き方を要求するということだ。 しかしこれはシンタックスの問題なので、非同期処理が簡単に書ける言語があれば何の問題も無い。

非同期処理を同期処理の切り替えが自由になるのは都合が良い。 例えば、JavaScriptではlocalStorageが同期APIなのだが、抽象化のために非同期APIでラップするとIndexed DBとの乗換えが楽になる。

もっと言うと、RPCを使ったコードを綺麗に書ける可能性を秘めている。 先ほどの.parのように簡単に切り替えられるとか。

最強の抽象化で勝負に出る

総合すると、スケーラブルな言語やフレームワークの要求仕様とは次のようなものだ。

  • Actionをデータとして扱う ⇒ 通信手段、再現性に対して柔軟
  • Dispatcherを使ったPub/Sub ⇒ 機能拡張に対して柔軟
  • Immutableなデータと純粋な関数を使う ⇒ 並行性、物理構成に対して柔軟
  • デフォルト非同期 ⇒ 同期処理と非同期処理の切り替え、通信手段に対して柔軟

最初から「疎結合」と言えばそれで済んだのかもしれないが、それではコードレベルに落ちないのでこれで良い。

あとはこれを超書きやすくするだけだ。

書きやすくなければ意味が無い。 特にImmutabilityや非同期処理の書きやすさは言語レベルのサポートがないと無理だ。 頑張れば出来るかもしれないが、やりたくない。 そろそろJavaScriptを捨てる時が来ているのかもしれない。

余談だが、Immutabilityはフロントエンドからサーバ、クラウド、インフラ、DB、どこへ持っていっても良いものだという感触がある。 色んな意味でリソースが贅沢に使えるようになったおかげだろう。

React.js+Fluxをやるなら今すぐElmを使うべき理由

皆さん、そろそろElmやりましょう。

Elmって何なの?

Webブラウザで動くFRP(Functional Reactive Programming)言語です。 コンパイルするとHTMLやJavaScriptを吐き出します。

Elm

公式サイトに動くサンプルが大量にあるので見てみると面白いです。

どうして今やるの?

これまでElmと言えば、良くも悪くも理想を追求した言語で、一般的なWebの部品(HTML/CSS/JavaScript)と相性が悪く、「まぁちょっとCanvas使っておもちゃアプリでも作るかー」くらいが関の山だったのですが、最近になってその状況は一変しました。

  • HTMLライブラリのサポート
  • Ajaxなど非同期タスクのサポート
  • JavaScriptAPIを通じて相互接続可能
  • エコシステムの登場

順序はちょっと忘れましたが、0.14とか0.15で色々出来るようになりました。

import Html exposing (..)
main = h1 [] [text "Hello, world!"]

Webブラウザで動くのにHTMLライブラリが無かったと言うのは意味が分からないかも知れませんが、実はElmは現在のWebの仕組みにとらわれずゼロから考えて作られています。冷静に考えると、そもそもマークアップ用の言語でアプリケーションを作るとか、そっちの方が意味が分かりませんよね?

本当は理想を突き進んでWebのしがらみを全部なくしてしまいたいのですが、現実問題としてはまだ普通にHTMLで組む方が楽できるよね、ということで使いましょうHTML。

あとはライブラリがそろっていない部分をNative(JavaScript)で補えるのも大きいです。 これで既存資産も活用できます。ちょろっと試したところWebSocketも出来ました。

React.jsやFluxと何の関係があるの?

一言で言うと、書き方が似ています!!

一応リアクティブ繋がりではあるんですが、最近リアクティブという言葉がバズりすぎていて最早この言葉を使う意味がありません。どちらもHTMLを宣言的に記述できるようになっています。

見るのが早いので見に行ってください。言語作者が自前で書かれているTodoMVCのソースです。

https://github.com/evancz/elm-todomvc/blob/master/Todo.elm

わずか約350行でTodoアプリ全体が表現されています。

Elm作者のEvanという方は以前からMVC的な設計を強く意識していて、コードに色濃く現れています。冒頭のコメントを以下に抜粋します。

 1. Model  - a full definition of the application's state
 2. Update - a way to step the application state forward
 3. View   - a way to visualize our application state with HTML
 4. Inputs - the signals necessary to manage events

React.js+Flux流に解釈するとおよそ次のようになります。

名前 役割
Model アプリケーションの状態を定義する。FluxのStoreに相当。
Update 状態を更新する。FluxのDispatcherに相当。
View 状態をHTMLとして表現する。ReactのComponentに相当し、FluxのActionを発行。
Inputs 入力を管理する。

と書いていますが、何か特別な言語仕様やフレームワークがあるわけではありません。単にそういう風に分けて設計しましたという話です。 実際にはAjaxなどでサーバとやり取りする必要が出るはずですが、大枠は変わらないと思います。

そして、DOMのレンダリングを高速化するためにVirtual DOMが使われています

実際、View部分の記述は本当にReact.jsのrender()そのままで、コンポーネントツリーの上から順にデータを渡して宣言的にHTMLを記述していきます。イベントをアクションとして発行して最終的にコンポーネントツリーにフィードバックさせる構造はFluxと同じです。

言語としてのアドバンテージ

React.jsをやっていると、JavaScriptの様々な言語仕様の壁にぶちあたります。 その点でもElmには以下のような追加の恩恵があります。

Immutability

JavaScriptでは新旧の値を比較する時、古い値を誤って更新しないために並々ならぬ努力が必要です。Immutabilityを実現するためにはライブラリで頑張るしかありません。

Elmの言語仕様は細かい点を除いてHaskellとほとんど同じで、変数ももちろんImmutableです。

型付け

Elmはコンパイル時に静的に型チェックを行います。 JavaScriptとの接続部分に関しては、ブート時にAPIが定義されていることをチェックするのと、Elm側にオブジェクトを渡す際に型のチェックが動的に入ります。

まとめ

React.js+Flux的な事がしたい人にとってElmは既に有力な選択肢です。 実際にプロダクションで使うには信頼と実績と見えない力が必要ですが、その辺は気合で何とかしましょう。

ついでと言っちゃなんですが、FRPの流儀が一緒に学べるのもおいしいところです。FRP単体のJavaScriptライブラリを導入するのはメリットが薄くて却下になることが多いと思うので。

というわけで、アーリーアダプターになってElmの実績を作っていきましょう。

以上。