Haskellのフロントエンド開発をEDSLでモダンにする

![output.gif](https://qiita-image-store.s3.amazonaws.com/0/297930/d1bacc5f-2856-76ea-83ea-c51037c99add.gif)

## 問題意識

モダンというと曖昧ですが、想定しているのはNode.jsのnpmで、pugやsassやtypescriptなど使い勝手の良いalt言語を自動ビルドしながら開発する感じです。

悲しいことに、Haskellのフロントエンド開発は忌避されがちです。
バックエンド（サーバー側）をHaskellで実装する場合、フロントエンド（クライアント側）は**どう開発すれば面倒くさくない**のでしょうか？

もし、SPAのようにJSON APIを介してバックエンドとフロントエンドを分離するなら、Haskellはバックエンドだけ、フロントエンドはnpmでビルド可能な自由な言語という選択ができます。
例えばservant+elmはいい感じですね。
この場合は、あまり問題にはなりません。

しかし、SPAは一般に管理ファイルとコード記述量が多くなります。
したがって、小規模でブラウザの動的処理が少ない場合は、SPAにしてしまうと逆に面倒です。
あるいは、SEOに問題があるかもしれません。

こういう場合に作るのは、ページ単位で（html, css, js）を返すような従来型のWebアプリです。
本記事では、主にHaskellで実装する小規模のWebアプリを対象に、フロントエンド開発をモダンにする方法を探ります。

## Yesod Shakespeare

Haskellには[Yesod](https://www.yesodweb.com/)というWebフレームワークがあり、Shakespeareという型安全なテンプレートエンジンを提供しています。

実のところ、YesodとShakespeareを使えば、自動ビルドしながらのフロントエンド開発は既に可能です。

しかし、Shakespeareの記法に不満を感じる人も多いのではないでしょうか。
下記の例は、[Yesod公式ページ](https://www.yesodweb.com/book/shakespearean-templates#shakespearean-templates_synopsis
)からの抜粋です。

Hamlet (htmlのalt言語)

```html
$doctype 5
<html>
    <head>
        <title>#{pageTitle} - My Site
        <link rel=stylesheet href=@{Stylesheet}>
    <body>
        <h1 .page-title>#{pageTitle}
        <p>Here is a list of your friends:
        $if null friends
            <p>Sorry, I lied, you don't have any friends.
        $else
            <ul>
                $forall Friend name age <- friends
                    <li>#{name} (#{age} years old)
        <footer>^{copyright}
```

Cassius (cssのalt言語)

```css
section.blog
    padding: 1em
    border: 1px solid #000
    h1
        color: #{headingColor}
        background-image: url(@{MyBackgroundR})
```

Julius (jsのalt言語)

```js
$(function(){
    $("section.#{sectionClass}").hide();
    $("#mybutton").click(function(){document.location = "@{SomeRouteR}";});
    ^{addBling}
});
```

好みの問題もあると思います。

しかし、私はこれらの記法に中途半端さを感じています。

- Hamlet: 生htmlのようで微妙に違う。。。
- Cassius: これはそんなに悪くないかも？
- Julius: ほぼ生js。。。

URLの埋め込みが特殊（#の代わりに@）で型安全なのは嬉しいですが、pugやsassやtypescriptなど使い勝手の良いalt言語を知っていると、そちらを使いたくなります。

alt言語が今ほど広まってなかった頃は、これで十分だったのかもしれません。
しかし、今のShakespeareは、Haskellerと非Haskellerのどちらから見ても微妙な言語という位置付けになっていると思われます。

## Heterocephalus

Shakespeare以外の解決として、pugやsassやtypescriptなどのalt言語をHaskellでうまいこと扱えるライブラリがありました。

- [heterocephalus - Haskellとフロントエンドが仲良くできるテンプレートエンジン](https://qiita.com/arowM/items/b0259f1ca43ef3192d31)

Heterocephalusを用いたフロントエンド開発は、大雑把に下記のようになると思います。

1. alt言語なんでも（heterocephalusの変数と制御文を埋め込む）を書く
2. npm-scriptsなどでビルド、失敗したら1.に戻る
3. （html, css, js）（heterocephalusの変数と制御文はそのまま）を得る
4. ghcでビルド、失敗したら1.に戻る
5. 実行して値を反映した（html, css, js）を得る

alt言語を選ばない汎用性がとてつもないです。
少しだけ触ってみました。

index.pug (heterocephalusの変数と制御文を埋め込む)

```pug
<!DOCTYPE html>
html
    head
        title test
    body
        h1 test
        ul
            | %{ forall a <- as }
            li \#{a}
            | %{ endforall }
```

index.html (heterocephalusの変数と制御文はそのまま)

```html
<!DOCTYPE html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <h1>test</h1>
    <ul>%{ forall a <- as }
      <li>#{a}</li>%{ endforall }
    </ul>
  </body>
</html>
```

ghci（Haskellで実行）

```console
> putStrLn . renderMarkup $ let as = ["x", "y"] in $(compileHtmlFile "index.html")
<!DOCTYPE html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <h1>test</h1>
    <ul>      <li>x</li>      <li>y</li>    </ul>
  </body>
</html>
```

※埋め込み文がalt言語に誤解されないよう書き方には工夫が必要です。

コンパイル時間はほとんどかかりませんでした。
したがって、npmとghcのビルドを連結した自動ビルドも現実的と思われます。

しかし、自動ビルドを走らせるには、alt言語から生成されるファイルの更新をうまいことghcに通知しないといけません。
これが厄介そうです。

あまり離れ業はしたくないのが私の気持ちです。

自動ビルドは諦めて、非Haskellerでも気持ちよくフロントエンド開発ができる環境を整えるのなら、このライブラリを使うと良さそうです。

## EDSL

Haskellのフロントエンド開発事情について、Shakesphereは今や微妙な解決、Heterocephalusは非Haskellerに優しい解決でした。
となると、Haskellerに優しい解決もあって良いはずです。

そこで、**EDSL**（言語内DSL）です。

Haskell Wikiに次のように書いてあります。

> [Embedded domain specific language - Haskell Wiki](https://wiki.haskell.org/Embedded_domain_specific_language)
>
> Embedded Domain Specific Language means that you embed a Domain specific language in a language like Haskell. E.g. using the Functional MetaPost library you can write Haskell expressions, which are then translated to MetaPost, MetaPost is run on the generated code and the result of MetaPost can be post-processed in Haskell.

一言でいうと、Haskellの文法で他の言語を記述できる仕組みです。

EDSLの良いところは、Haskellの文法を一つ知っているだけで、他の言語が扱うさまざまな領域を操作できる点です。
また、Haskellの関数や値をそのまま利用できるのも利点です。
Haskellでは、モナドという数学的背景のある枠組みで、EDSLを使うことができます。

ShakesphereもEDSLの一つですが、これは独自言語を記述してそれをパースするので、モナドのEDSLではありません。
Haskellに埋め込めはしますが、そのものは外部言語です。

本記事で扱いたいのは、内部言語（モナド）で記述して、他の言語を生成するようなEDSLです。

そこで、（html, css, js）を生成する良さそうなEDSLライブラリを探しました。
見つけたライブラリをモダンなalt言語と対応付けした表が下記です。

| 使い勝手の良いalt言語 | HaskellのEDSL    |
| ------------------------- | ---------------- |
| pug                       | lucid            |
| sass                      | clay             |
| typescript, ... etc      | sunroof, ... etc |

それぞれ簡単に紹介します。

### Lucid (htmlのEDSL)

概要は以下の記事が分かりやすいです。

- [HTMLテンプレート？Lucidを使ってみた！](https://qiita.com/lotz/items/04d0bd8bf37df1f852fb)

コード例

```haskell
html_ $ do
    head_ $ do
        title_ [] "Introduction page."
        link_ [rel_ "stylesheet", type_ "text/css", href_ "screen.css"]
    body_ $ do
        div_ [id_ "header"] "Syntax"
        p_ [] "This is an example of Lucid syntax."
        ul_ [] $ mapM_ (li_ [] . toHtml . show) [1, 2, 3]
```

> [Lucid: templating DSL for HTML](https://chrisdone.com/posts/lucid)

pugのように見えるでしょうか。

繰り返しには```mapM_```を使います。
includeは、ただの関数切り出しで実現できます。

### Clay (cssのDSL)

まだ日本語の記事がないみたいですが、[Githubレポジトリ](https://github.com/sebastiaanvisser/clay)の星は結構ついてます。

コード例

```haskell
header |> nav ?
  do background    white
     color         "#04a"
     fontSize      (px 24)
     padding       20 0 20 0
     textTransform uppercase
     position      absolute
     left          0
     right         0
     bottom        (px (-72))
```

> [CLAY UNLIMITED* CSS](http://fvisser.nl/clay/)

sassのように見えるでしょうか。

要素セレクタと結合子、属性と値などが定義されています。
メディアクエリや自動ベンダープレフィックスにも対応していて高機能です。

mixinは、ただの関数切り出しで実現できます。

### Sunroof (jsのDSL)

これは見つけるのにとても苦労しました。
LucidやClayと同じように、内部呼び出しが可能な（コード内で結果を得られる）ものに限定した結果、Sunroofが残りました。
経緯は脚注[^1]にメモしてあります。

コード例

```haskell
sunroofCompileJSA def "main" $ do
    function $ \ n -> do
        return (n * (n :: JSNumber))
```

> [Sunroof Monadic Javascript Compiler](https://github.com/ku-fpg/sunroof-compiler)

CoffeeScriptのように見えるかもしれません。

CanvasやJQerryなどのAPIが定義されています。
しかし、htmlタグや属性などは自分で文字列を書く必要があります。

実のところ、使い勝手にはあまり満足してません。
ちょっとした動きを実装するのには使えそうな気がします。

## EDSLでフロントエンドのコードを書く

以上のEDSLライブラリを使って、フロントエンドのコードを書いてみます。

Lucidのモナドの中で、ClayとSunroofをコードをインラインで埋め込む形で書きます。

```haskell:Html.hs
lucidHtml :: MonadIO m => HtmlT m ()
lucidHtml = do
    doctype_
    html_ $ do
        head_ $ do
            -- ......
            style_ [type_ "text/css"] $ render clayCss
        body_ $ do
            -- ......
            js <- liftIO sunroofJs
            script_ [type_ "text/javascript"] js
```

```haskell:Css.hs
clayCss :: Css
clayCss = do
    body ? do
        sym margin  zero
        sym padding zero
        fontSize (px 16)
        backgroundColor "#c0c0c0"
    -- ......
```

```haskell:Js.hs
sunroofJs :: IO String
sunroofJs = sunroofCompileJSA def "main" content

content :: JSA ()
content = do
    log (string "Debug") console
    alert (string "Hello World!")
```

各EDSLの記述が、Haskellの文法で関数的に結合されていて見通しが良いですね。


## idとclassを直和型で一元管理してみる（好奇心）

せっかくEDSLなので、Haskellの直和型を使ってidとclassの名前を一元管理してみます。
手間のわりに嬉しいことは少ないかもしれませんが、試してみます。


```haskell:IdClass.hs
{-# LANGUAGE FlexibleInstances           #-}

-- | 各DSLの属性型に変換する関数を定義するための型クラス
class ToAttribute a where
    toAttr :: IdClass -> a

-- | 型クラスのインスタンス実装（使うDSLライブラリによって実装を変える）
-- | html（lucid）
instance ToAttribute Attribute where
    toAttr (Id v) = id_ . pack . kebab . show $ v
    toAttr (Class v) = class_ . pack . kebab. show $ v

-- | css（clay）
instance ToAttribute Selector where
    toAttr (Id v) = selectorFromText . mappend "#" . pack . kebab . show $ v
    toAttr (Class v) = selectorFromText . mappend "." . pack . kebab . show $ v

-- | js（sunroof）
instance ToAttribute JSString where
    toAttr (Id v) = string . mappend "#" . kebab . show $ v
    toAttr (Class v) = string . mappend "." . kebab . show $ v

-- | idとclassを表現する型
data IdClass = Id I | Class C
    deriving (Show, Eq)

-- | idを表現する型（自分で定義する）
data I
    = ActionButton
    deriving (Show, Eq)

-- | classを表現する型（自分で定義する）
data C
    = Container
    | Button
    deriving (Show, Eq)
```

### 適用前

Lucid

```haskell
body_ $
    div_ [class_ "container"] $
        h1_ [] "haskellでフロントエンド開発"
```

Clay

```haskell
".container" ? do
    sym2 padding 0 (em 1)
    h1 ? do
        color "#6144b3"
        fontSize (px 32)
```

Sunroof 略

### 適用後（タイポセーフ）

Lucid

```haskell
body_ $
    div_ [toAttr (Class Container)] $
        h1_ [] "haskellでフロントエンド開発"
```

Clay

```haskell
toAttr (Class Container) ? do
    sym2 padding 0 (em 1)
    h1 ? do
        color "#6144b3"
        fontSize (px 32)
```

Sunroof 略

できました。

## 自動コンパイルしてみる

モダンなフロントエンド開発にはタスクランナーが必須です。
これをHaskellでやる方法を探ります。

### yesod devel

Yesodの自動ビルドは、Yesod以外でも使うことができます。

- [Non-Yesod development](https://github.com/yesodweb/yesod/tree/master/yesod-bin#non-yesod-development)

やり方は大雑把に下記です。

1. 自分のアプリから[Application](http://hackage.haskell.org/package/wai-3.2.1.2/docs/Network-Wai.html#t:Application)型を切り出す。
    （Haskellの大抵のWebフレームワークにはこの型があります）
2. Devel.hsという名前のファイルを作成し、[DevelExample.hs](https://github.com/yesodweb/yesod/blob/master/yesod-bin/devel-example/src/DevelExample.hs)のdevelMain関数をよしなにコピペする。
3. yesod-binをダウンロード&ビルド

    ```sh
    stack build yesod-bin
    ```
4. yesod develサーバーを起動

    ```sh
    stack exec -- yesod devel
    ```

思ったより簡単に導入できます。

しかし[Why is Yesod so slow - Reddit](https://www.reddit.com/r/haskell/comments/3d9i9q/why_is_yesod_so_slow/)の質問のとおり、ビルドする度に少々時間がかかってしまいます。
回答によると、ghciを使うと速いらしいので試してみます。

### ghci

私がYesod知らずなだけで、既にYesodのScaffoldに含まれていました。
例えば```stack new PROJECTNAME simple```で入るやつです。
[DevelMain.hs](https://github.com/yesodweb/yesod-scaffold/blob/simple/app/DevelMain.hs)が提供されています。

Yesode以外で使うには、DevelMain.hsを自分のプロジェクトにコピペして、yesod develと同じくAppliction型を切り出して、update関数よしなに書き換えます。

そして下記のように実行します。

1. ghciを起動

    ```sh
    stack ghci
    ```
2. DevelMain.hsを読み込む

    ```sh
    > :l app/DevelMain.hs
    ```
3. アプリを再起動（一瞬！）

    ```sh
    > DevelMain.update
    ```
4. ファイルを編集する
5. ファイルの変更読み込み（これもほぼ一瞬！）

    ```sh
    > :r
    ```
6. 3.に戻る

速すぎて感動しました。
yesod develとは何だったのか。。。
しかし、DevelMain.hsのコメントにあるように、ghciは非常に残念な欠点を抱えています。

> This option provides significantly faster code reload compared to
@yesod devel@. However, you do not get automatic code reload
(which may be a benefit, depending on your perspective)

読み込みが高速な一方、更新は手動になるようです。
replなので当然といえば当然です。

でもそこは対応して欲しかった。
あと一歩のところで諦めきれず、別でうまいことやっている例はないものかと探しました。

### ghcid

そしたらなんと、見つけました！

- [ghcid for the win!](http://www.parsonsmatt.org/2018/05/19/ghcid_for_the_win.html)

ソースコードを更新すると、自動的に```:r```（再読み込み）を実行するghciのデーモンだそうです。

動かし方は下記です。

1. ghcidをインストール

    ```sh
    stack install ghcid
    ```
2. ghcidをオプション付きで起動

    ```sh
    ghcid --command "stack ghci" --test "DevelMain.update"
    ```
    ※ --test: コンパイルが成功するたびに呼ばれる関数を指定

    ※ DevelMain.update: 前項のghciで使ったものと同じ

たったこれだけで、高速なghciの読み込みが自動化されました！
ヾ(＠⌒▽⌒＠)ﾉﾜｰｲ!

## ブラウザも自動リロードしてみる

最後にブラウザの自動リロードもつけます。
ここはnpmの力を借ります。

browser-syncをインストール

```sh
npm install browser-sync -g
```

ファイル更新を監視しつつ、アプリサーバーをプロキシ

```sh
browser-sync start --files src/ --proxy APPSERVER:PORT
```

※本来はビルド成功時に更新されるファイルを監視すべきですが、いまは手っ取り早く```src/```を指定しています。

※アプリサーバーのポートが3000の場合、browser-syncサーバーのポートは1ずれて3001で起動します。

結果、npm-scriptsでできるような自動更新をHaskellで実現することができました。
d(`･ω´･+)ﾔｯﾀﾈ+.☆ﾟ+.☆ﾟ

## ソースコード

[GitHubレポジトリ](https://github.com/opyapeus/frontend-haskell)に置きました。

## まとめ

本記事では、Haskellのフロントエンド開発をEDSLでモダンにする試みを紹介しました。

細かい点には触れていないので、実際に使ってみるとやや微妙に感じるところがあるかもしれません。
それでも、HaskellでWebアプリを作るときの知見の一つにはなったのかなと思います。

- Haskellのフロントエンド開発にEDSLを使う選択肢があるよ
- Yesod以外でも自動ビルドはできるよ
- ghcid使うと自動ビルドがすごく速いよ

## 補足

使い勝手に不満が残ったJSのEDSLですが、先進的な解決（フレームワーク）としてMisoやReflexなどがあるようです。
これらも検討してみた方が良さそうです。

## 追記 2018/11/9

要素のクラスを変更することだけに特化したJSのEDSLライブラリを作りました。
[jmonkey🐒](https://github.com/opyapeus/jmonkey)
機能は限られていますが、ちょっとした動きを実装するのに役立つかもしれません。
しかし、少し複雑なフロントエンドの実装をするときは、やはり何かフレームワークに頼った方が良いような気がします。

[^1]: JSのEDSL [The JavaScript Problem - HaskellWiki](https://wiki.haskell.org/The_JavaScript_Problem#Haskell_-.3E_JS)に概要が書いてあります。最初はHasteを使おうと思ったのですが、[Can't run a Haskell interpreter in a Haste program](https://github.com/valderman/haste-compiler/issues/74)の説明によると、外部呼び出し以外に生成コードを得る良い方法はないようです。他にもFay, GHCJS、JSaddleなどを検討しましたが、どれも同じ事情があるようでした。そんな中で、唯一Sunroofが内部呼び出し可能なEDSLだったので、ひとまずSunroofを使うことにしました。