PureScript + OutWatchを試してみる

以前、[PureScript + Halogenを試した](https://qiita.com/cedretaber/items/9f649c7e8eaf5aaf4bb7)のですが、それとは別に、OutWatchというJSフレームワークがあります。

https://outwatch.github.io/

> A polyglot functional & reactive UI framework

と謳っており、リアクティブなUIフレームワークであるらしいです。
面白そうですね。

polyglotと付いているとおり、複数のAltJSに対応しています。
現状では **PureScript** と **ScalaJS** に対応しています。

なるほど、使ってみましょう！
PureScriptの方でやってみます。

## ライブラリのインストール

```bash
$ pulp init
$ bower install purescript-outwatch
$ npm install rxjs snabbdom
```

この辺は公式サイトに載っていると通りのやり方ですね。
私がやった時は何故か「QuickCheckが無い！」って怒られたのでquickcheckを別途入れました。PureScript自体のインストールについては、別記事を御覧ください。

## Hello, world!

コードを書きます。

```haskell
module Main where

import Prelude
import Control.Monad.Eff (Eff)

import OutWatch.Tags (h1) as H
import OutWatch.Attributes (text)
import OutWatch.Core (VDOM, render) as OutWatch

main :: ∀ e. Eff (vdom :: OutWatch.VDOM | e) Unit
main = OutWatch.render "#app" (H.h1 [text "Hello, world!"])
```

次に、アプリをマウントするHTMLを書きます。

```html
<!DOCTYPE html>
<html>
    <head>
        <title>OutWatch Sample</title>
    </head>
    <body>
        <div id="app" />
        <script type="text/javascript" src="./bundle.js"></script>
    </body>
</html>
```

そして、PureScriptのコードをトランスパイルします。

```bash
$ pulp browserify --to bundle.js
```

では適当なサーバを立てて動かしてみましょう。

無事、 "Hello, world!" の文字が表示されれば成功です。

## 足し算アプリを作る

もう少しロジックのあるアプリを作ります。
インプットフィールドが2つあって、両方に入った数値を足し合わせてくれるアプリを作ってみましょう。

こんなコードになりました。

```haskell
module Main where

import Prelude
import Data.Int (fromString)
import Data.Maybe (fromMaybe)
import Control.Monad.Eff (Eff)

import OutWatch.Dom.VDomModifier (VDom)
import OutWatch.Tags (div, input, span) as H
import OutWatch.Attributes (text, tpe, inputString, value, childShow, (:=), (<==), (==>))
import OutWatch.Core (VDOM, render) as OutWatch
import OutWatch.Sink (createStringHandler)

import RxJS.Observable (combineLatest, startWith)
    
main :: ∀ e. Eff (vdom :: OutWatch.VDOM | e) Unit
main =
    OutWatch.render "#app" app

    where

    sum :: String -> String -> Int
    sum sx sy =
        (t sx) + (t sy)
        where
        t = fromMaybe 0 <<< fromString

    app :: ∀ f. VDom f
    app =
        let
            xHandler = createStringHandler ["0"]
            yHandler = createStringHandler ["0"]

            state = combineLatest
                sum xHandler.src yHandler.src
                # startWith 0

            mkInput handler =
                H.input
                    [ tpe := "text"
                    , value <== handler.src
                    , inputString ==> handler
                    ]
        in
        H.div
            [ mkInput xHandler
            , H.span [ text " + " ]
            , mkInput yHandler
            , H.span [ text " = " ]
            , H.span [ childShow <== state ]
            ]
```

要素ごとに見ていきましょう。

### エントリーポイント

```haskell
main :: ∀ e. Eff (vdom :: OutWatch.VDOM | e) Unit
main =
    OutWatch.render "#app" app
```

メイン関数の型はだいたい同じですね。
主要な処理は別に切り出しています。

### アプリ本体

アプリの実体は以下の部分です。

```haskell
    app :: ∀ f. VDom f
    app =
        let
            xHandler = createStringHandler ["0"]
            yHandler = createStringHandler ["0"]

            state = combineLatest
                sum xHandler.src yHandler.src
                # startWith 0

            mkInput handler =
                H.input
                    [ tpe := "text"
                    , value <== handler.src
                    , inputString ==> handler
                    ]
        in
        H.div
            [ mkInput xHandler
            , H.span [ text " + " ]
            , mkInput yHandler
            , H.span [ text " = " ]
            , H.span [ childShow <== state ]
            ]
```

上から順番に見ていきましょう。

### Handler

```haskell
            xHandler = createStringHandler ["0"]
            yHandler = createStringHandler ["0"]
```

`createStringHandler` 関数で（VDomEffモナドに包まれた）Handlerを作ります。
Handlerというのは、

```haskell
type Handler eff a =
  { src :: Observable a
  , sink :: Observer eff a
  }
```

という定義からも分かる通り、ソースとなるObservableとシンクとなるObserverとを持ったレコードです。

つまりこのHandlerで、入力される2つの値（ここではxとyと置く）を管理します。

### Combine

次に、xとyとの合計であるstateを作り出します。

```haskell
            state = combineLatest
                sum xHandler.src yHandler.src
                # startWith 0
```

`combineLatest` は2つのObservableを合成し、新たなObservableを作り出す関数です。最初の引数には、合成する2つのObservableから取り出した値をどのように加工するかを指定する関数を与えます。
ここで与えているsum関数の定義は、

```haskell
    sum :: String -> String -> Int
    sum sx sy =
        (t sx) + (t sy)
        where
        t = fromMaybe 0 <<< fromString
```

で、これは「2つの文字列を取って、数値に変換し、足し合わせる（数値に変換できなかったら0として扱う）」という処理を行っています。

なので、stateは「xHandler、またはyHandlerの値が更新されるたびに、両方の値を取ってきて、それを数値に変換し、合計する」というObservableになります。

なお、 `# startWith 0` は初期値を設定している部分です。
`a # f` は `f a` と等価です。

### HTML

HTMLの組み立ては内部DSLで行います。

```haskell
        H.div
            [ mkInput xHandler
            , H.span [ text " + " ]
            , mkInput yHandler
            , H.span [ text " = " ]
            , H.span [ childShow <== state ]
            ]
```

概ね見た目通りの挙動をします。
気になるのは `childShow <== stete` の部分でしょうか。
これは、Observableなstateから新しい値が来たら、その値を取得して子要素として描写する、という意味です。要するに、stateが変化する度に中身の値も連動して書き換わるわけですね。

なお、 `child` と `childShow` とがありますが、違いは、描写する前に `show` 関数をかませるか否かです。

### Input

Inputフィールドの生成用に関数を作っています。

```haskell
            mkInput handler =
                H.input
                    [ tpe := "text"
                    , value <== handler.src
                    , inputString ==> handler
                    ]
```

ここで見える `value <== handler.src` は、上記の `childShow` と同じようなものですね。 `handler` のソースであるObservableが更新される度にvalueを書き換えます。

では `inputString ==> handler` は何でしょうか？
これは、矢印の向きからも分かる通り、 `<==` の逆です。そのフィールドに入力された値が変化する度に、 `handler` にその新しい値を流し込んでいきます。流し込まれた値はObservableの次の値となり、そのObservableを監視している全ての箇所に値を伝播します。

### 動かしてみる

同じようにjsにトランスパイルし、動かしてみましょう。

文字の入力に応じて、ちゃんと合計値が変化しているでしょうか。
また無効な入力は、0として扱われているでしょうか。

## まとめ

OutWatchを使って非常に簡単なアプリを作ってみました。
とても小規模なフレームワークで、正直これだけで画面を作るのは難しいと思います（生のReactに近い印象。無数に作られるであろうHandlerの管理どうするの？　とか、ルーティングは？　とか）。
なのですが、大変興味深いです。

ScalaJS版もあるようなので、皆さん是非とも使ってみましょう。