elm+electronで気楽に始めるFRP


インタラクティブなトイアプリを作る必要があったので
electronとFRPが前提のHaskell風味AltJSであるelmを使って作ってみた。
さらにNativeなAPIを使う方法についても調査し、
今回調査した知見をもとに
[elmtrnというボイラープレートを作った](https://github.com/yasuyuky/elmtrn)

## Quickstart

``` bash
git clone git@github.com:yasuyuky/elmtrn.git
cd elmtrn
npm install
elm make -y
gulp
```



上記のようにすれば[公式のclockサンプル](http://elm-lang.org/examples/clock)を
謎の時計ウィジェットにしたものが立ち上がる。
![elmtrn - Atom 2015-09-01 16-55-40.png](https://qiita-image-store.s3.amazonaws.com/0/41862/3eea7292-1fd8-9834-fcea-1217627037d0.png "elmtrn - Atom 2015-09-01 16-55-40.png")


# elm + electron

elmで書いたものをelectronで動かすのは想像していたより簡単で
electronのhello worldに含まれるような
以下のような標準的な`app.js`で指定してるhtmlの部分を
elmで生成されるhtmlに置き換えるだけであっさり動いた。

```app.js
var app = require('app');
var BrowserWindow = require('browser-window');
var mainWindow = null;

app.on('window-all-closed', function() {
    app.quit();
});

app.on('ready', function() {
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadUrl('file://' + __dirname + '/app.html');
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});
```

上記のように基本的にはメインプロセスはjsで
あとはレンダラープロセスで描画するhtmlを
elmでコンパイルしたものにするというのが簡単だと思う。

メインプロセスのjsもelmで書きたいという向きもあるかもしれないが、
簡単なSPAっぽいアプリを書く分にはメインに関してはほぼ定型文で
BrowserWindowに渡すパラメータを調整するくらいだと思うので
今のところ個人的にはこれで満足している




# elmのビルド

elmは仮想DOMをアレコレするみたいなので
elmでSPAを作る場合は基本的には全部elmで完結させて開発するのがいいのかなと思う。
（とゆうか他のフレームワークと混ぜるとか出来なさそう）
それも踏まえた上で自動ビルドとlivereloadをしたかったので
[ぼくのかんがえたさいきょうのElectron](http://qiita.com/Quramy/items/90d61ff37ca1b95a7f6d)を参考にして[gulpfileを書いた。](https://github.com/yasuyuky/elmtrn/blob/master/gulpfile.coffee)



# elmで状態を扱う際のアーキテクチャ

公式のサンプルの時計を動かしたりする分には必要無いが
現実世界のアプリケーションを書くためには当然状態を扱うことになる。
そうすると途端にFRPに特有のアーキテクチャのパターンで実装することになる。

[公式でも紹介されているToDoアプリ](https://github.com/evancz/elm-todomvc)が非常に参考になる。

- `model`と呼ばれる状態を定義するデータ型
- `action`
- Signalにそって`model`を更新する`update`
- `model`からアウトプットを生成する`view`

をそれぞれ分けて定義し、
信号の流れを一方通行にするデザインパターンが有効で、
上記アプリもそのパターンで書かれている。

これは[React周辺ではfluxと呼ばれるアーキテクチャ](http://qiita.com/koba04/items/b32ba449d753fdb2b597)になるのだと思う。
自分の理解では以下の感じの対応になる

|  elm    |      flux       |
|---------|-----------------|
| actions | Action Creators |
| update  |   Dispatcher    |
|  Model  |      Store      |
|  view   |   React Views   |


viewに関してはelmもReact.js同様にVirtualDOMでよろしくやってくれるはず。

**ちなみにelmtrnではこのパターンにそって公式のclockサンプルを
fluxライクなスタイルに書き直してある。**

## StartApp

上記アーキテクチャのボイラープレートをまとめてくれた[start-appというライブラリがある](https://github.com/evancz/start-app)。
これを使うことで自動的に上記のアーキテクチャに沿った設計が出来る。

しかし、本当に薄いラッパーでしかないので、
その部分を自分で書いても大した負担にはならないし、
後述のような任意のマウスイベントや時間変化のシグナルを扱う
場合に想定から外れてしまうので最終的には自分は使わなかった。

## 自分が定義した以外のSignalも扱うには？

`Signal`を`foldp`してる場所に扱いたいSignalを
マージするのがいいっぽい。

```elm
model = Signal.foldp update initialModel actions.signal
```

↓

```elm

model = Signal.foldp update initialModel signals

signals = Signal.mergeMany [ actions.signal
                           , UpdateTime <~ every second 
                           ]

type Action = NoOp
            | UpdateTime Time

```

`<~` (Signal.map) で Signal Time から Signal Actionに変換している



Signalを扱う場所を一箇所に集めるのはアーキテクチャとして
正しいというのもあるけど、そもそも上記以外で
（例えば一番最初のサンプルのようにmainのなかで）
Signalを扱おうと思うとエラーを吐いて動かしたアプリがハングしたりしたので
素直にデザインパターンに従うのがいいかと思う。


## do記法の不在

Haskellを使った事がある人がelmの文法を眺めると`do`記法で
逐次的に処理を記述するような記法がないことに気付くかもしれない。
これはおそらく意図的な設計だと思う。

実際にfluxのようなアーキテクチャを採用すると、逐次的な処理というのは
actionからの一方通行のSignalのループを回す事によって実現し、
do記法による逐次処理のようなものはそのルールを壊す事になるので相性がよくない。

do記法がないので必然的にモナド則に沿ってインスタンスを作っても
そこまでうれしさがないし、リスト内包記法みたいなものも排除されている。
列挙が必要なものは引数としてリストをとるようになっている。


# elmでNativeモジュールを通してElectronのAPIを使う

ところでelectronにはデスクトップアプリとしての機能を実現する[API群がある](http://electron.atom.io/docs/v0.31.0/#api-references)。

これをElmから使うにはどうしたらいいかという問題があるが、
[ElmのNative moduleを書く](http://qiita.com/philopon/items/cbc2066242bac6f66af0)を参考にラッパーを書くことで使えるようになる。

レンダラープロセスでつかうAPIのうち、例えばClipboardを扱うためのAPIの
ラッパーは以下のようになる。

```Clipboard.js
var clipboard = require('clipboard');

Elm.Native.Clipboard = {};

Elm.Native.Clipboard.make = function(elm) {
  elm.Native = elm.Native || {};
  elm.Native.Clipboard = elm.Native.Clipboard || {};
  if (elm.Native.Clipboard.values) return elm.Native.Clipboard.values;

  function readText(_) {
    return clipboard.readText();
  }

  function writeText(s) {
    clipboard.writeText(s);
    return s;
  }

  return elm.Native.Clipboard.values = {  // Export
    readText: readText,
    writeText: writeText
  };

};

```

注意したい点として、上記readTextは環境からクリップボードを読み込むものなので
本来的には引数が必要ないはずだが、Nativeモジュールの制約で引数をとるようになっている
使う際は`Native.Clipboard.readText ()`のようにユニット（空のタプル）を渡すのがいいかと思う。



# 所感など

elmよくできてる。HaskellっぽいけどFRPを前提に言語設計されてて
モナドとかも出てこないので気軽にHaskell風味シンタックスに慣れるにもいいのでは。
拡張もわりと素直に書ける。

FRPのアーキテクチャというか設計パターンは一度覚えてしまうと
GUI設計は全部これでやりたくなる。

個人的にはphpにおけるhtmlや様々な言語における生SQLなどのように
言語内に他の言語のシンタックスが入り込むのがあまり好きでないし
その点でReact.jsはJSXを前提としててあまり好きでなかった（あくまで個人の感想です）。

elmはelm-htmlのライブラリなどを使えばviewの部分の記述を
言語として違和感のシンタックスで書けていい。
少なくともそんなに大きなものでないならHaskell由来の
簡潔なシンタックスでコンパクトにかける印象がある。

elm-htmlについてももう少し書こうかと思ったけど力尽きた…