概要
Haskellの勉強用にIHaskellを検討したがなんだか面倒そうなので、超簡単なGHCi用のkernelを自分で作ってHaskellの勉強をしようと考えた。
簡単なkernelなので機能は限定されるかもしれませんし、まだHaskellの勉強を始めたばかりなのでどんな機能が必要かもわかっていませんが作ってみました。
実行環境
> sw_vers
ProductName: macOS
ProductVersion: 15.4.1
BuildVersion: 24E263
> ghci --version
The Glorious Glasgow Haskell Compilation System, version 9.12.2
> python --version
Python 3.12.7
> jupyter --version
Selected Jupyter core packages...
IPython : 8.27.0
ipykernel : 6.28.0
ipywidgets : 7.8.1
jupyter_client : 8.6.0
jupyter_core : 5.7.2
jupyter_server : 2.14.1
jupyterlab : 4.2.5
nbclient : 0.8.0
nbconvert : 7.16.4
nbformat : 5.10.4
notebook : 7.2.2
qtconsole : 5.5.1
traitlets : 5.14.3
超簡単なkernel作成
kernel作成のドキュメントからサンプルをコピー
上記からEcho Kernelのソースをコピーします。
from ipykernel.kernelbase import Kernel
class EchoKernel(Kernel):
implementation = 'Echo'
implementation_version = '1.0'
language = 'no-op'
language_version = '0.1'
language_info = {'mimetype': 'text/plain'}
banner = "Echo kernel - as useful as a parrot"
def do_execute(self, code, silent, store_history=True, user_expressions=None,
allow_stdin=False):
if not silent:
stream_content = {'name': 'stdout', 'text': code}
self.send_response(self.iopub_socket, 'stream', stream_content)
return {'status': 'ok',
# The base class increments the execution count
'execution_count': self.execution_count,
'payload': [],
'user_expressions': {},
}
if __name__ == '__main__':
from ipykernel.kernelapp import IPKernelApp
IPKernelApp.launch_instance(kernel_class=EchoKernel)
{"argv":["python","-m","echokernel", "-f", "{connection_file}"],
"display_name":"Echo"
}
ファイル構成
echo/
└─ kernel.json
kernelのインストール。echoはディレクトリ名
> jupyter kernelspec install echo --user
これらに若干の変更を加えてGHCi用のkernelを作ります。
echo kernelを変更してGHCi用のkernel作成
名前はghciにします。
ファイル構成
ghci/
├─ kernel.json
└─ logo-64x64.png
{
"argv": ["python", "-m",
"ghci_kernel", "-f",
"{connection_file}"],
"display_name": "GHCi",
"language": "haskell",
"env": {
"PYTHONPATH": "/Users/xxxx/yyyy"
}
}
-
ghci_kernelはこれから作るkernelのソース名 -
display_nameはJupyter notebookのkernel名に表示される -
PYTHONPATHはこれから作るghci_kernel.pyがあるディレクトリ名で、もしPathがすでに通っているならenvは不要
logo-64x64.pngはJupyter Labでlauncherに表示される画像で、64x64ピクセルの画像です。無くてもOK。
kernel情報のインストール
> jupyter kernelspec install ghci --user
[InstallKernelSpec] Installed kernelspec ghci in /Users/xxxx/Library/Jupyter/kernels/ghci
kernel作成
from ipykernel.kernelbase import Kernel
from pexpect.replwrap import REPLWrapper
class GHCiKernel(Kernel):
implementation = 'GHCi'
implementation_version = '0.0.1'
language_info = {
'name': 'haskell',
'mimetype': 'text/x-haskell',
'file_extension': '.hs'
}
language_version = '9.12.2'
banner = 'Simple GHCi Kernel'
replwrapper = REPLWrapper("ghci", "ghci> ", None,
continuation_prompt="ghci| ")
def do_execute(self, code, silent, store_history=True,
user_expressions=None, allow_stdin=False):
if not silent:
stdout = self.replwrapper.run_command(code, timeout=None)
stream_content = {'name': 'stdout', 'text': stdout}
self.send_response(self.iopub_socket, 'stream', stream_content)
return {'status': 'ok', 'execution_count': self.execution_count,
'payload': [], 'user_expressions': {}}
if __name__ == '__main__':
from ipykernel.kernelapp import IPKernelApp
IPKernelApp.launch_instance(kernel_class=GHCiKernel)
基本的にはREPLWrapperを使って、Jupyterの入力Cellの内容をrun_commandに渡し、その結果をJupyterに渡すだけです。echo kernelとそんなに変わりません。以下が各プロパティに加えて追加したものです。
from pexpect.replwrap import REPLWrapper
replwrapper = REPLWrapper("ghci", "ghci> ", None,
continuation_prompt="ghci| ")
stdout = self.replwrapper.run_command(code, timeout=None)
stream_content = {'name': 'stdout', 'text': stdout}
次のURLがREPLWrapperのドキュメントです。
REPLWrapperの引数
- "ghci": 起動する対話型Haskellインタプリタ
- stackを使っているときは"stack ghci"または"stack repl"でもOK
- "ghci> ": プロンプト
- continuation_prompt="ghci| " : 継続入力のプロンプト
以下のGHCiを起動した時のプロンプトを上記の設定に使いました。
> ghci
GHCi, version 9.12.2: https://www.haskell.org/ghc/ :? for help
ghci> :set +m
ghci> let f :: Int -> Int
ghci| f x = x + 10
ghci|
ghci> f 1
11
テスト
まだ、勉強始めたばかりなので、以下のIHaskellにあるコードでエラー起きないように一部変更して実行しました。特にブロック(複数行)にしないとエラーになるコードは:set +mを設定してletまたは:{ :}を使いました。
ちゃんとHaskellのロゴが表示されています
また、下記の画像の右上にkernel名GHCiが表示されています。
入力の[11],[12],[13]は最後に空行を追加しています。これはブロックを終了させるためです。
追記)
フィボナッチ数列はfib 0 = 0のようですが、上記のようにIHaskellのサンプルコードはfib 0 = 1となっています。画像の変更はしません。以降のテストも同様です。
改良
if not silent:
+ if self.execution_count == 1:
+ code = ":set +m\n" + code
+ if not code.endswith("\n"):
+ code += "\n"
stdout = self.replwrapper.run_command(code, timeout=None)
毎回最初に:set +mをCellから実行するのは面倒なので、プログラムで実行します。入力の最後が改行でないとき改行を追加していますが、ブロックの最後は空行が必要なためです。:{ :}を使う場合は関係ないですが。
改良版のテスト
空行を追加しなくても、ちゃんと動作しました。
終わりに
たった10行にも満たないコードの追加で簡単なGHCi用のkernelが出来ました。しかし、問題もあります。fib 100を実行したら全然終わらなかったのでRestart Kernelを実行してもfib 100を実行しているGHCiは終了せずもう1個起動してたので、結果GHCiが2個動いている状態でした。よって、fib 100を実行していたprocessをkill -KILL pidで強制終了させました。
次回の投稿はこれに対処して、Interrupt Kernelにも対応する予定ですが、上記のコードだけでも入門レベルの学習用には使えると思います。
改良版と機能アップ版
改良版
改良版をさらに機能アップ




