4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HaskellのJupyter用IHaskellは高機能だけどインストールが面倒なので超簡単なGHCi用のkernelを作ろう

Last updated at Posted at 2025-05-12

概要

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のソースをコピーします。

echokernel.py
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)
kernel.json
{"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
kernel.json
{
    "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作成

ghci_kernel.py
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または:{ :}を使いました。

haskell01.png

ちゃんとHaskellのロゴが表示されています
また、下記の画像の右上にkernel名GHCiが表示されています。

haskell02.png

haskell03.png

haskell04.png

入力の[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から実行するのは面倒なので、プログラムで実行します。入力の最後が改行でないとき改行を追加していますが、ブロックの最後は空行が必要なためです。:{ :}を使う場合は関係ないですが。

改良版のテスト

haskell05.png

空行を追加しなくても、ちゃんと動作しました。

終わりに

たった10行にも満たないコードの追加で簡単なGHCi用のkernelが出来ました。しかし、問題もあります。fib 100を実行したら全然終わらなかったのでRestart Kernelを実行してもfib 100を実行しているGHCiは終了せずもう1個起動してたので、結果GHCiが2個動いている状態でした。よって、fib 100を実行していたprocessをkill -KILL pidで強制終了させました。
次回の投稿はこれに対処して、Interrupt Kernelにも対応する予定ですが、上記のコードだけでも入門レベルの学習用には使えると思います。

改良版と機能アップ版

改良版

改良版をさらに機能アップ

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?