servant-haskellをCloud Runにのせて結び目不変量を求めるWebアプリを作る

夏休みですね。[^1]
みなさん夏休みの工作の進捗はいかがでしょうか？
servant-haskellとVueを触りたくなり、お絵かきアプリを作ってみました。

# この記事で紹介すること
- haskellを使った位相幾何学アプリケーションの設計
- Cloud Run[^2]を使ったservant-haskellアプリのデプロイ

経験が浅いのでトンチンカンなことを言っているかもしれませんが、ご容赦ください。

# 対象とする人
エンジニア・数学徒など、どなたでも
インフラ周り、サーバサイドのアルゴリズムからフロントのUIまで、なんでもコメントいただけると嬉しいです。
フロント側の設計、実装は[別記事](https://qiita.com/ayanamizuta/items/f5a5f8726cd3443fa1b1) にまとめました

# 作成物のデモ
- 結び目を描いて、[カウフマンブラケット](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%82%A6%E3%83%95%E3%83%9E%E3%83%B3%E5%A4%9A%E9%A0%85%E5%BC%8F)を求めます[^3]
- [ソース](https://github.com/ayanamizuta/jones)(github)
- [アプリ](https://ayanamizuta.com/jones)(Cloud Runをホスティングしたもの[^4])

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちょっと機能を付けてデプロイしました（リバースプロキシの都合でもっさりします...）！<a href="https://t.co/1B41FS6QCz">https://t.co/1B41FS6QCz</a><br>内容薄いですが、記事も書きます <a href="https://t.co/4hUFYG7iJ6">pic.twitter.com/4hUFYG7iJ6</a></p>&mdash; penpenpen (@ayanamizuta) <a href="https://twitter.com/ayanamizuta/status/1168157334943617024?ref_src=twsrc%5Etfw">September 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 設計

フロント側（Vue.js）でお絵かき機能を実装し、お絵かきしたデータをservant側に送って[^5]カウフマンブラケットを計算してフロントに返す。

# 実装

servant-haskellのわかりやすい入門は@lotzさんの[【型レベルWeb DSL】 Servantの紹介 ](https://qiita.com/lotz/items/883b41fa79f060e59efa "【型レベルWeb DSL】 Servantの紹介")をご参照ください。

## サーバー
まず上のような結び目の図（射影図などと呼ばれます）からカウフマンブラケットを計算するために必要なデータ構造を考えてみましょう。
射影図をじっと見つめていると結び目たちが「交点を頂点としたグラフ」に見えてくると思います。これをHaskellの型を使って実装すると次のような感じになると思います。

``` haskell
data Front = Front {tagf :: Int} deriving (Show,Eq)
data Back = Back {tagb :: Int} deriving (Show,Eq)
data Intersection = InterSection {edges :: (Front,Front,Back,Back)} | Trivial deriving (Show,Eq)
type Link = [Intersection]
```

あとはいい感じに[Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%82%A6%E3%83%95%E3%83%9E%E3%83%B3%E5%A4%9A%E9%A0%85%E5%BC%8F)にある通りに再帰でカウフマンブラケットを求めれば良いです。

## フロント
お絵かきして計算に必要なデータをajaxでservantに送れるようにします。
詳細は[別記事](https://qiita.com/ayanamizuta/items/f5a5f8726cd3443fa1b1)にて

# デプロイ
以下のDockerfileをcloud buildでビルドした後、Cloud Runの画面かgcloudコマンドでデプロイできます。

- Dockerから外部に通ずるポートを環境変数PORTに入れる
- 80,443以外のポートを指定する

ことに気をつければOKです。

Dockerfileのビルドを2回にしなくとも[Multi-stageのDockerfileをCloud Buildでビルドするコツ](https://qiita.com/bigplants/items/73f4e16b840207f09e81)にmulti-stagedなDockerfileのビルドを一発でstage毎に別リポジトリにpushできる方法が紹介されていますが、未着手です。

Dockerfile（ビルド用）

``` Dockerfile
FROM haskell:8.6.5

ADD https://api.github.com/repos/ayanamizuta/jones/git/refs/heads/master version.json
RUN git clone https://github.com/ayanamizuta/jones.git

WORKDIR ./jones
RUN stack setup
RUN stack build
RUN stack --local-bin-path /sbin install
```
Dockerfile（デプロイ用）

``` Dockerfile
FROM びるど
WORKDIR ./jones
RUN mkdir bin
COPY /sbin/jones ./bin/
EXPOSE 8080
ENV PORT 8080
CMD ["bin/jones"]
```

# 参考にした記事等
[【型レベルWeb DSL】 Servantの紹介 ](https://qiita.com/lotz/items/883b41fa79f060e59efa "【型レベルWeb DSL】 Servantの紹介")

[Multi-stageのDockerfileをCloud Buildでビルドするコツ](https://qiita.com/bigplants/items/73f4e16b840207f09e81)

# わからなかった事
- 以下のようなDockerfileでデプロイしようとしたところ、entrypointの `stack run` 起動時にghcをインストールしてしまうようでした（現在のデプロイはバイナリを叩いて動いています）。`stack build`時に（起動時にinstallされてしまうghcと同じversionの）ghcがinstallされるのに、なぜ...

``` Dockerfile
FROM びるど
EXPOSE 8080
ENV PORT 8080
CMD ["stack","run"]
```

[^1]: 仕事の最繁忙期でした（しみじみ）。
[^2]: 当初GAEを勉強したくてGAEにデプロイしようとしたのですが、個人で利用するには料金がそこそこするのでCloud Runにしました。この記事と同じ方法でGAEにもデプロイできることを確認しています。
[^3]: 厳密に言うとこれは結び目不変量ではないです。結び目不変量であるところのJones多項式を求める機能まで作ろうとしましたが、デプロイに時間がかかりpendingしました...
[^4]: もっさりしますが、リバースプロキシをfirebase functionsで立てたためです(リージョンがus-centralしかない...)
[^5]: DB使うわけでもないのでサーバーサイドの実装はいらないんですが、haskell使いたかったしGAEへのデプロイもやってみたかったので今回の構成にしました。