Julia の Jupyter Notebook を他人に試してもらうためのツールとして Binder を活用する

# 本日は

- Julia は Python に比べるとまだ人口が少ないので「こういうのをJuliaで作ったよ！」とSNSで宣伝するとみんな（Julia界隈）が喜びます．作ったらそれがSOTA(state of the art) なんです．ただし，動かす側からだとそのスクリプト，ノートブックを動かすのにそれをダウンロードして必要に応じてJupyter Notebookを立ち上げるという一手間が肉に塩を振る以上に手間がかかってしまうことです．環境を構築している間にSNSで動いてる！しゅごいいぃと感じた熱意が指数関数的に現象してしまいます．パッケージが入ってなくて途中でエラーを吐いて動かなくなったら尚更です．また，閲覧しているデバイスがパソコンとは限らないので可能ならスマホ，タブレットでみたユーザーがパソコンにログインして動かすというのも手間っちゃ手間です．


- さて，グラフ描画ライブラリの Julia ラッパーを提供する GR.jl 開発者のツイートでこういうのがありました．

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">By popular demand from the <a href="https://twitter.com/hashtag/JuliaLang?src=hash&amp;ref_src=twsrc%5Etfw">#JuliaLang</a> community, binary packages for <a href="https://twitter.com/hashtag/raspberrypi?src=hash&amp;ref_src=twsrc%5Etfw">#raspberrypi</a> and <a href="https://twitter.com/hashtag/archlinux?src=hash&amp;ref_src=twsrc%5Etfw">#archlinux</a> are now available for GR. The new features include the drawing of composite paths (arcs, Bézier curves, etc.) as well as marker symbols with borders. See <a href="https://t.co/jiK0sL5IOO">https://t.co/jiK0sL5IOO</a></p>&mdash; Josef Heinen (@josef_heinen) <a href="https://twitter.com/josef_heinen/status/1222187182418464775?ref_src=twsrc%5Etfw">January 28, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

リンクをクリックするとbinderというアイコンが出てきて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/5d373ffb-6795-7de5-aa7f-1d600723f3eb.png)

Jupyter Notebook が見つかって

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/6516aa02-96c5-07ab-0c90-a7c46d6cb2ca.png)

anim.ipynb を選択して動かすと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/bea9669b-2191-3a41-8f6f-5d58bae4ee33.png)

動くんですけど！！！，マジかブラウザ上でJulia動かせるんか！ iPhone や iPad で動くんか！(実際に動きます).


# ということで

- Binder の使い方を紹介します．Binderを使うと既存の公開されているリポジトリにあるJupyter Notebookをブラウザ上で動かすことができます．
- Julia に限らずPythonやRといった言語も活用できるようです．例えば [ipywidgets のnotebook形式のドキュメント](https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html) などもbinderを立ち上げて利用することができます．例えばhttps://github.com/jupyter-widgets/ipywidgets にある `launch binder` のアイコンをクリックすることでPythonの環境が立ち上がり，そこで動かすことができます．

# 基本的な使い方（利用する側として）

まず，どういうことができるのかを体感するために利用する立場としてBinderを触ってみましょう．

## mybinder にアクセス

https://mybinder.org/ にアクセスします．下記の図のような画面で次の要素を指定します．

- 動かしたいノートブックが置いてあるGitHubのリポジトリ
- 対応するブランチ(基本masterで良い)
- リポジトリを起点としたノートブックの置いてあるパスを指定（指定しなければリポジトリ全体をブラウザ上で見渡すことができる）

launch ボタンを押してしばらく待つと環境が立ち上がります．環境はDockerのコンテナが動くようです．動かしたいリポジトリが有名であれば誰かがイメージをビルドしているので（Binderが溜めている？）キャッシュを利用して環境が立ち上がります．個人開発者のこじんまりしているものであればイメージのビルドが走るログが流れるのでコーヒーを淹れながら待ちましょう．


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/73fc1bf5-dbd6-d9c7-7e0a-479a3a45880a.png)

## 変換できるリポジトリ

- [Configuration Files](https://mybinder.readthedocs.io/en/latest/config_files.html) の項目を見るとリポジトリのルートに`setup.py`, `requirements.txt` があれば Python の環境をBinderが用意するようです．`Project.toml` があればJuliaの環境を用意するようです．あとで紹介しますが，`Dockerfile` があればそのファイルをビルドするようです．

> In the majority of cases, providing your own Dockerfile is not necessary as the base images provide core functionality, compact image sizes, and efficient builds. We recommend trying the other configuration files before deciding to use your own Dockerfile.

このようにリポジトリのルートにある設定ファイルを認識するようです．もし，リポジトリに `binder` というディレクトリがあれば Binder がBinder用の設定ファイルとして認識するようですね．これは開発する側から見ると共有に必要な設定を記述する設定ファイルと開発用の設定ファイルを区別するために役に立ちそうです．

参考: [FAQ](https://mybinder.readthedocs.io/en/latest/faq.html#can-i-put-my-configuration-files-outside-the-root-of-my-repository) 

> Yes! Configuration files may be placed in the root of your repository or in a binder/ folder in the root of your repository (i.e. myproject/binder/). If a binder/ folder is used, Binder will only read configuration files from that location (i.e. myproject/binder/requirements.txt) and will ignore those in the repository’s root (myproject/environment.yml and myproject/requirements.txt).

- もしリポジトリのメンテナーがBinderを知っている場合は `launch binder` というアイコンがリポジトリにあると思うのでそこをクリックすればOKです
  - [ipywidgets](https://github.com/jupyter-widgets/ipywidgets)
  - [bqplot](https://github.com/bloomberg/bqplot)

# 基本的な使い方（開発・公開する側として）

- 自分の作ったリポジトリを他の人に動かしてもらう手段として Binder を活用する方法を書きます．上でも書きましたように特に気張らなくても Binder がいい感じで認識するようなのでなんとなくトライするで済むかもしれません．
- もっと込み入った環境，例えば，PackageCompilerXで重いパッケージのロードを軽くするようにしたJuliaのシステムイメージを使ってもらいたいとなるとなんも考えずにmybinder にアクセスしてよし何してもらうのは難しいでしょう．おそらくそれを実現するにはDockerfileを書くことになります．
- ここではDockerfileによってカスタマイズした環境をBinderに持っていく方法を紹介します．ここでは開発・公開する側はDockerfileの作り方，コンテナ操作などは既知と仮定して話を進めます．手元にDockerfileがあってそれを使うと手元の環境では公開する対象の一通りの動作は確認できるんだけれど，それをどうBinderに認識させるのかがわからない人向けです．

## ドキュメントを読む

- 実は [Use a Dockerfile for your Binder repository](https://mybinder.readthedocs.io/en/latest/tutorials/dockerfile.html) に全てが書いてあります．まずはこちらを読むことをお勧めします．
- Binderの使用上コンテナ内部でrootユーザーになっているようなものは受け付けないようですので `uid is 1000` となるユーザーを作ることになります．ドキュメントにもあるように

```Dockerfile
ARG NB_USER=jovyan
ARG NB_UID=1000
ENV USER ${NB_USER}
ENV NB_UID ${NB_UID}
ENV HOME /home/${NB_USER}

RUN adduser --disabled-password \
    --gecos "Default user" \
    --uid ${NB_UID} \
    ${NB_USER}
```

を追記しておきます．`jovyan` はBinderを動かしている際のユーザー名になるようです．手元で動かすときは `-v, --volume` オプションで必要なディレクトリをマウントしてると思いますが，Binderで動かしておくには必要な成果物を `COPY` でイメージの中に入れておくことになります．パーミッションの解決のために `chown` コマンドでリポジトリの所有者を解決しています．これも Binderのドキュメントに書いてある通りです．

```Dockerfile
# Make sure the contents of our repo are in ${HOME}
COPY . ${HOME}
USER root
RUN chown -R ${NB_UID} ${HOME}
USER ${NB_USER}
```

## さて，Exampleは？

- 拙著の MyWorkflow.jl をご覧ください．[Dockerfile はこちら](https://github.com/terasakisatoshi/MyWorkflow.jl/blob/v0.5.0/Dockerfile)．特徴は下記の通り
  - PackageCompilerX.jl でパッケージをビルドする
  - jupytext で ipynb ではない形式のテキストをJupyterNotebookのフォーマットに自動で変換するようにしている．
    - これでバージョン管理をしやすくしている
  - 必要なパッケージを導入している

- Binder対応のためユーザーをrootと設定してたのでBinderドキュメントの通りにユーザーを追加するにくわえています．

### 引っかかったところ

- PackageCompierX.jl を使ったビルドの操作がルート権限じゃないとビルドできなくて
- 一方でビルドは通るが一般ユーザー(jovyan) から使おうとすると実行できない．ひとまず所有権を一般ユーザーにすると使えるようになりました．

```Dockerfile
# Do Ahead of Time Compilation using PackageCompilerX
# For some technical reason, we switch default user to root then we switch back again
USER root
RUN julia --trace-compile="traced.jl" -e 'using OhMyREPL, Revise, Plots, PyCall, DataFrames' && \
    julia -e 'using PackageCompilerX; \
              PackageCompilerX.create_sysimage([:OhMyREPL, :Revise, :Plots, :GR, :PyCall, :DataFrames]; precompile_statements_file="traced.jl", replace_default=true) \
             ' && \
    rm traced.jl
# Make NB_USER Occupy julia binary
RUN chown -R ${NB_UID} /usr/local/julia
# Swich user again to NB_USER
USER ${NB_USER}
```

 多分，一般ユーザーとしてjuliaのバイナリーをインストールすると行けるんだと思いまあすが，ベースイメージが julia:1.3.1 を使ってるので上の方法を採用しています．
- あとは jupytext が `.jl` を `ipynb` と認識してくれなかったので `c.NotebookApp.contents_manager_class = 'jupytext.TextFileContentsManager'` を `jupyter_notebook_config.py` に追記することで解決しました．[jupytext のリポジトリ](https://github.com/mwouts/jupytext) には書いてあるのですがルート権限だとなくてもできていたのでハマりました．

```Dockerfile
RUN jupyter notebook --generate-config && \
    echo "\
c.ContentsManager.default_jupytext_formats = 'ipynb,jl'\n\
c.NotebookApp.contents_manager_class = 'jupytext.TextFileContentsManager'\n\
c.NotebookApp.open_browser = False\n\
" >> ${HOME}/.jupyter/jupyter_notebook_config.py
```

## Binder用に変換した結果

[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/terasakisatoshi/MyWorkflow.jl/master) <- をクリックするとみられます．初回のビルドには結構時間がかかりますね．

## Binder の良いところ

- Jupyter Notebook が動くのは多くありますが, Interact.jl， ipywidgets のコンポーネントが動作するのが魅力的です．しかも無料で使うことができます．
- 似たような仕組みとして Google のコラボラトリー がありますが， ipywidgets が動かない制限があるはずです．
    - （実は動いてたら教えてください．）
    - 回避策としてFormを使うという手段があるようです．[Jupyter NotebookとGoogle Colaboratoryを使い比べてみた](https://www.tdi.co.jp/miso/jupyternotebook-googlecolaboratory)

例えばこういうことができます．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/45cbd333-0418-901c-8f8e-cdb1d115e15d.png)


# まとめ

Binderで成果物を共有する方法を書きました．Julia，Python, Rに限らず，Jupyter Notebook で動作する言語であれば同様にできると思うので皆さんトライしてみてください．