Io 言語の Jupyter Kernel を作る with Docker
Docker 使いながら,Io 言語の Jupyter Kernel を作ったので,そのときのメモを書いておく.
作った Kernel は Dockerイメージとして置いときました.
いきさつ
何回か前の記事で書いたんだが,「7つの言語、7つの世界」という書籍の Jupyter notebook を作りたい. が,Io の Kernerl だけなかったので,(ついに)作ろうかなと.
Io
7つの言語の書籍の2つ目に紹介されていた言語. 公式サイト曰く,純粋なプロトタイプベースの言語らしい.
(書籍曰く)プロトタイプベースの言語の最も有名な言語は Javascript であり,要するにオブジェクトをどんどんコピーして新しいオブジェクト(クラス)を定義していくプログラミングスタイルらしい. Javascript は機能がすごい豊富だが,Io はものすごくコンパクトに作られている. まぁ,興味があったら,書籍の Io の章だけ呼んでみると面白いと思う.
Jupyter notebook
すごくリッチな REPL というイメージ. データサイエンティストはよく使うらしい(Python とかを).
うれしいのは,REPL の履歴をファイルとして残せること. 7つの言語の書籍は特に,REPL 形式での例題が多いので,相性が良いかなと思ってる.
作る
- You can reuse the IPython kernel machinery to handle the communications, and just describe how to execute your code. This is much simpler if the target language can be driven from Python. See Making simple Python wrapper kernels for details.
- You can implement the kernel machinery in your target language. This is more work initially, but the people using your kernel might be more likely to contribute to it if it’s in the language they know.
IPyhorn でラップして作るか,Io で直接作るか,の2択. 後者の方が難しいけど,カッコいい(?)ので挑戦しようとしたが ぜんぜんよくわからなかった ので,諦めて前者にした.
前者の場合は日本語の記事も含めて,情報が豊富なので助かった.
- Making simple Python wrapper kernels — jupyter_client 5.1.0.dev documentation
- Jupyter の Egison 簡易カーネルを自作してみた。 - Qiita
- Jupyterのkernelを作ってみる - Qiita
IPython をラップする形で Kernel を作るには、Jupyter(Python) と,ターゲット言語(今回では Io)が必要である. ローカルでやってもいいんだけど, Windows 的にはつらいものが多い ので Docker でガチャガチャする.
開発環境
- ホストOS : Windows 10 Home
- Docker : 1.12.3
- Python : 3.6.1
- Jupyter : 4.3.0
- Io : 2015.11.11 (v.20140919)
Dockerfile を作る
前述したとおり,Jupyter + Io が必要.
いくつかのやり方が考えられる.
- Jupyter のイメージ + Io をインストール
- Io のイメージ + Jupyter をインストール
- Python のイメージ + Jupyter と Io をインストール
上から順に試したところ,3番でなら難なくできた. ちなみに,1 は Io をビルドするとこける. 2 は Jupyter をインストールするときにこける. 理由も良くわからなかったので,どんどんやり方を変えてったら 3 でうまくいった.
以下が Dockerfile
FROM python:latest
ENV HOME /root
WORKDIR $HOME
RUN pip install ipython jupyter
# Io
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake \
g++ \
gcc \
libyajl-dev \
libpython3.4-dev \
libgmp-dev \
libmemcached-dev \
make \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*RUN git clone --branch 2015.11.11 --depth 1 https://github.com/stevedekorte/io.git ~/io \
&& mkdir -p ~/io/build \
&& cd ~/io/build \
&& cmake .. \
&& make install \
&& rm -fr ~/io
EXPOSE 8888
CMD ["jupyter", "notebook", "--no-browser", "--allow-root", "--ip='0.0.0.0'"]
Docker for Windows のせいか, --ip='0.0.0.0'
というオプションを追加しないとうまくいかなかった.
これで,jupyter + Io の環境ができた.
$ docker build -t iio .
$ docker run -it -v /c/Users/hoge/git/python/iio:/root/iio -p 8888:8888 --name=test iio /bin/bash
カレントディレクトリを /root
以下に同期してる.
Kernel を書く
基本的に,「Jupyter の Egison 簡易カーネルを自作してみた。 - Qiita」の記事を参考にして egison
を io
に置き換えただけ. プロンプトのところだけ,Io は Io>
となるので,そう変えた. 他にも,いらんかなぁというところ(versionをパターンマッチさせてるとことか)は削除してる.
from ipykernel.kernelbase import Kernel
from pexpect import replwrap, EOF
from subprocess import check_output
import re
import signal
= re.compile(r'[\r\n]+')
crlf_pat
class IoKernel(Kernel):
= 'Io'
implementation = '0.0.1'
implementation_version
= {
language_info 'name': 'Io',
'codemirror_mode': 'scheme',
'mimetype': 'text/plain',
'file_extension': '.io'
}
= None
_language_version
@property
def language_version(self):
if self._language_version is None:
self._language_version = check_output(['io', '--version']).decode('utf-8')
return self._language_version
@property
def banner(self):
return u'Simple Io Kernel (%s)' % self.language_version
def __init__(self, **kwargs):
__init__(self, **kwargs)
Kernel.self._start_io()
def _start_io(self):
= signal.signal(signal.SIGINT, signal.SIG_DFL)
sig try:
self.iowrapper = replwrap.REPLWrapper("io", "Io> ", None)
finally:
signal.signal(signal.SIGINT, sig)
def do_execute(self, code, silent, store_history=True,
=None, allow_stdin=False):
user_expressions= crlf_pat.sub(' ', code.strip())
code if not code:
return {'status': 'ok', 'execution_count': self.execution_count,
'payload': [], 'user_expressions': {}}
= False
interrupted try:
= self.iowrapper.run_command(code, timeout=None)
output except KeyboardInterrupt:
self.iowrapper.child.sendintr()
= True
interrupted self.iowrapper._expect_prompt()
= self.iowrapper.child.before
output except EOF:
= self.iowrapper.child.before + 'Restarting Io'
output self._start_io()
if not silent:
# Send standard output
= {'name': 'stdout', 'text': output}
stream_content self.send_response(self.iopub_socket, 'stream', stream_content)
if interrupted:
return {'status': 'abort', 'execution_count': self.execution_count}
return {'status': 'ok', 'execution_count': self.execution_count,
'payload': [], 'user_expressions': {}}
# ===== MAIN =====
if __name__ == '__main__':
from IPython.kernel.zmq.kernelapp import IPKernelApp
=IoKernel) IPKernelApp.launch_instance(kernel_class
これを iokernel.py
という名前でカレントディレクトリで保存した.
次に,以下のような kernel.json
というファイルを作成した.
{
"display_name": "Io",
"language": "io",
"argv": ["python", "/root/iio/iokernel.py", "-f", "{connection_file}"],
"codemirror_mode": "scheme"
}
これを,適当な名前のディレクトリ(今回は ./iokernel/
)の下に置く. で,/root/iio
で jupyter kernelspec install kernel.json
を実行すると,Io の Kernel が Jupyter notebook に登録される.
実行
Docker 内で jupyter notebook --no-browser --allow-root --ip='0.0.0.0'
と実行し,設定されている IP アドレスにアクセスすると Jupyter が起動し,右上の New に Io が追加されているはず.
Dockerイメージ化
./iokernel.py
と ./iokernel/kernel.json
をマウントしたり,jupyter kernelspec install
を実行したりを追加する.
前に書いた Dockerfile の EXPOSE
の行の前に以下を追加した.
WORKDIR $HOME/iio
ADD . $HOME/iio
RUN jupyter kernelspec install iokernel
これで,docker run
するだけで,Io 入りの Jupyter のコンテナがイメージが出来上がった. やった.
おしまい
何十番煎じだよというネタだったが,まぁメモなのでご勘弁を. もう少し,改良するとして,これで全ての言語の Jupyter Kernel が揃った.
あとマージするだけ. どーやるんだろ...?