LoginSignup
53
50

More than 5 years have passed since last update.

elm+electronで気楽に始めるFRP

Last updated at Posted at 2015-09-02

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

Quickstart

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

上記のようにすれば公式のclockサンプル
謎の時計ウィジェットにしたものが立ち上がる。
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を参考にしてgulpfileを書いた。

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

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

公式でも紹介されているToDoアプリが非常に参考になる。

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

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

これはReact周辺ではfluxと呼ばれるアーキテクチャになるのだと思う。
自分の理解では以下の感じの対応になる

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

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

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

StartApp

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

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

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

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

model = Signal.foldp update initialModel actions.signal


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群がある

これをElmから使うにはどうしたらいいかという問題があるが、
ElmのNative moduleを書くを参考にラッパーを書くことで使えるようになる。

レンダラープロセスでつかう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についてももう少し書こうかと思ったけど力尽きた…

53
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
50