🪿

(付録(仮))WSLのUbuntuにHaskellの開発環境を構築する

2025/02/25に公開

Haskellの開発環境

まずはじめに、Haskellの開発環境を導入する時に必ず目にする言葉の概略を把握しておきましょう。

  • StackとCabalはプログラム開発支援ツール
  • GHCはコンパイラ
  • HLSはランゲージサーバー

以上の3つのものが、Haskellのプログラミング開発で相互に関連しあって利用されます。そしてこれら3つは合わせて、Haskell Toolchain と呼ばれています。

そしてこれらに加えて、エディタのHaskellコードの作成支援設定があります。

Haskell Toolchain

このHaskell Toolchainについて、ひとつづつ、もう少し詳しく見ていきます。

StackとCabalはプログラム開発支援ツール

これはどちらもプログラム開発支援ツールです。実際には両方同時に使うわけではなく、どちらか一方の好きな方を使うことになります。

ネットの解説などをみると、少し複雑で難しそうな雰囲気があります。しかし、Haskell入門時のプログラム作成は、これらStackとCabalを一切使わなくても十分に行えますので、初めは、プログラムの開発支援のツールというものがHaskellにもあるということを把握しておくだけで大丈夫です。

GHCはHaskellのコンパイラ

Haskellを始める時に、Haskell Toolchainの中で必ず把握しておかなければならないものが、このGHC(Glasgow Haskell Compiler)です。実際、最低限GHCだけ準備できれば、Haskellの入門的な学習が始められます

Haskellは、Python等のスクリプト言語と違って、C言語のようにソースファイルをコンパイルして実行ファイルを作る言語です。そして、GHCと呼ばれるものがHaskellのコンパイラです。実際には、コンパイラだけでなく、基本的なモジュールや、ghci(インタラクティブなプログラム環境)も提供してくれます。

HLS(Haskell-language-server)

HLSは、Haskellのプログラムコードを書く時に、エディタと連携して様々な支援をしてくれるプログラムです。HLSは基本的な関数の補完だけでなく、コードを書いている最中に関数や型の構造を説明してくれたり、エラーになっている部分を指摘してくれたり、冗長になっている部分を完結に書いたコードを提案してくれたりと、Haskellを学習していく場合にも役に立つ機能を提供してくれます。

このようなコード支援プログラムは各言語毎にあり、これらを一般的にlanguage serverと呼んでいます。また、このlaugage serverとエディタの連携の仕様の事、もしくは、その機能のことがLSPと呼ばれています。

Haskell Toolchainの管理はGHCupで決まり

Haskellの学習を始める場合のオススメHaskell開発環境は、UbuntuのシステムにおけるHakellパッケージの環境ではなくて、GHCupというHaskellの開発環境開発ツールによる開発環境です。

このGHCupによるHaskellの開発環境は、ユーザー領域にroot権限なしで(sudoを使わない)作成でききます。すなわち、GHCupの開発環境だと、システムに影響を及ぼさないので、自分の都合で気軽に作ったり消したりできるのです。初心者の場合、「色々と試したときに失敗してもやり直せるから大丈夫!」という安心感はとても重要な要素と思うので、オススメです!

もちろん、この安心感だけがオススメ要素というわけではありません。GHCupは、Haskell Toolchainの全てのインストールとバージョン管理(複数バージョンの切り替え)を簡単に行えるようにする超便利なプログラムであり、ここ最近でのHaskell Toolchain管理ではスタンダードのツールだと思います。

そこで、この記事でもGHCupを使ったHaskell環境構築を紹介します。

エディタ環境の準備

Haskellの開発環境の準備に関しては、Haskell Toolchain の次に、実際にコードを書いていくエディタの環境を準備する必要があります。

初学者が使いやすい、また、ネットでのHowTo情報が多いエディタとしては、VSCodeがあります。また、プログラミングオタクの間では、Neovimも人気があります。

それぞれは有名なエディタなのでエディタそのものの解説は省き、ここでは、エディタでHaskellのコードを書くときに必要となる設定の具体例、特にHLSとの連携の仕方を紹介します。

以上が、この記事で紹介していく環境の概略でした。ここから具体的にそれぞれのインストールや設定の説明をしていきます。

GHCupのインストールとHaskell Toolchainの管理

https://www.haskell.org/ghcup/

GHCupは、Ubuntuのパッケージとしてではなく、本家で配布されているものを直接ユーザー領域にインストールして利用します。

インストール前のUbuntu側の準備

GHCupのインストール作業を始める前に、Ubuntuに以下のパッケージをインストールしておきましょう。既に入っている場合に上書きされても問題はないので、以下のコマンド(最新のUbuntuの場合)を実行して確実にパッケージをインストールしておきましょう。

prompt
sudo apt install build-essential curl libffi-dev libffi8 libgmp-dev libgmp10 libncurses-dev pkg-config

GHCupのインストール

GHCupのインストールは以下のコマンドで行います。

prompt
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh

そして、ここからいくつかの質問があります。


GHCupのインストールのオープニング

PATHの設定

Detected zsh shell on your system...
Do you want ghcup to automatically add the required PATH variable to >"/home/neko/.zshrc"?

「あなたのシステムはzshですね。ghcupに自動で必要なPATHを"/home/neko/.zshrc"に加えさせますか?」と聞いています。答えは、.zshrcの先頭に(prepend)、もしくは末尾に(append)追加、または、手動でPATHを設定するためにNoの選択肢があります。これはどれでも問題はないですが、とりあえず、デフォルトの先頭に追加にしておきましょう。

ただ、どれを選んだとしても、以下の二つのディレクトリが環境変数PAHTに追加されいることが必要だという認識を持っておきましょう。

  • $HOME/.ghcup/bin
  • $HOME/.cabal/bin

GHCupには、$HOME/.ghcup/envにこのディレクトリをPAHTに追加するスクリプトがあって、これを読み込む設定を~/.zshrc等に追加しています。

HLSのインストール

Do you want to install haskell-language-server (HLS)?
HLS is a language-server that provides IDE-like functionality
and can integrate with different editors, such as Vim, Emacs, VS Code, Atom, ...
Also see https://haskell-language-server.readthedocs.io/en/stable/

「haskell-language-server(HLS)をインストールしたいですか?....」と聞いています。

後で、設定するのでここではデフォルトの「n」(リターンキーのみ)でOKです。

Stackで使うGHCの設定

Do you want to enable better integration of stack with GHCup?
This means that stack won't install its own GHC versions, but uses GHCup's.

「stackにGHCupを統合した機能を有効にしたいですか? これは、stackが自身でGHCをインストールしないようにして(通常は、stack自身がGHCをインストールする)、代わりにGHCupで管理しているGHCを利用するようにします。」

この質問には「y」を選択しましょう。

必須パッケージの確認

実は、ここで表示されるUbuntuのパッケージがインストールされていることがGHCupを使うのに必須です。この記事で作業を進めている場合、一番初めに解決しているはずなので、そのまま「Enterキー」を押して作業を進めましょう。

メッセージ通りここから、少しだけ時間がかかるので、コーヒーでも飲みながらインストール作業を見守ります。

インストール作業の完了


インストール作業の完了

上記のようなメッセージが出るとインストール作業の完了です。

In order to run ghc and cabal, you need to adjust your PATH variable.
To do so, you may want to run 'source /home/neko/.ghcup/env' in your current terminal
session as well as your shell configuration (e.g. ~/.bashrc).

「ghcやcabalを実行するために、環境変数PATHを調整する必要があります。そのために、現在のターミナルセッション上では'source /home/neko/.ghcup/env'を実行するとよいかもしれません。例えば~/.bashrcで設定して起動したのと同様のことをしてくれます。」とありますが、これは、先に行った環境変数PATHの設定が必要という話です。インストール直後のセッションでは、PATHが通っていないかもしれないので、exec zshで.zshrcを読み込みなおす等しましょうという注意喚起メッセージです。

ghcup tui

GHCupのおすすめの使い方はTUIです。以下のコマンドを実行しましょう。

prompt
ghcup tui

以下が実行画面の様子ですTUIは「Terminal User Interface」の略です。

GHCupで管理されている、GHCやHLS等の一覧が表示され、チェックが入っているものがインストールされているもので、ダブルチェックが入っているものが現在選択されているものです。GHCupを使えば、とりあえず複数のバージョンをインストールしておき、その中で好きなものを一つ選択することで切り替えて使うことが出来るのです。


ghcup tuiの実行画面

操作は、カーソルキーでカーソル(色がついている行)を移動させることができます。上下はループしているので、GHCupから、Cabal、Stack、HLS、GHCと見て回ってください。

AtCoder用の GHC 9.4.5 もインストールする

GHCの項目のあたりにカーソルを移動してください。現在、GHC9.4.8がおすすめとしてインストールされ、かつ、選択されています。

ここで、競技プログラミングのAtCoderのジャッジシステムで使われているGHCのバージョンである9.4.5をインストールして、自分の環境でもこのバージョンを使えるようにしましょう。

しかし今、GHCup上のGHCの一覧を見るとインストールしたいバージョン9.4.5が見当たりません。そんな時には、「a」キー(allのa)を押します。この 「a」キーによって、GHCupが扱えるすべてのバージョンが表示されます。もう一度「a」キーを押せば、主なものに限定されて表示されます。では、バージョン9.4.5の行にカーソルを持って行き、「i」キー(installのi)を押しましょう。バージョン9.4.5のGHCのインストールが始まります。インストール作業が終わると行末にPress enter to cotinueと出るのでエンターキーを押して、TUIに戻ります。

9.4.5がインストールされたら、9.4.5の行にカーソルを持って行き、「s」キー(setのs)を押します。またPress enter to cotinueと出るのでエンターキーを押して、TUIに戻ります。これで、GHCは9.4.5が利用されるようになりました。行頭にダブルチェックが入って選択された状態になっているのを確認してください。


GHC 9.4.5を普段使いにする

この選択された状態のGHCのバージョンが、シェルからghcを呼んだときに使われます。

StackやCabalを利用する場合には、そのプロジェクトの設定(GHCのバージョン等の設定)に従います。一方で、気軽に好きなディレクトリ内で簡単なプログラムを書く場合、つまり、アルゴ式やAtCoderの簡単な練習コードをStack等の支配下にない好きなディレクトリで書く場合に、このGHCupで選択されているGHCのバージョンでコンパイルが行われると把握しておけばOKです。

さて、ついでにTUI上のキー操作のおさらいです。

キー 機能
i インストール
u アンインストール
s 使うものとして選択
a 全部表示・主なもの表示の切り替え

ちなみにTUI下段にいつでもキーの機能が表示されています。

HLSの最新版をインストール

ghcup tuiを起動して、HLSの項目を確認します。
一番上におすすめ最新2.9.0.1というバージョンがると思うのでカーソルで選択して「i」キーでインストールしましょう。インストールが完了したら「s」キーでこのHSL2.9.0.1を選択します。

そして、選択できた状態で、GHCの項目のあたりにカーソルを持って行って下さい。

各バージョンの右側にhls-powerdと表示されている行とされていない行があることがわかります。表示されている行は、そのGHCのバージョンが現在選択されているHLSを使えるのですが、されていない行にあるGHCのバージョンはHLSが使えません。


各GHCがHLSに対応されているかの確認

GHC 9.4.5の行にはhls-powerdの表示がないので、つまりは、GHC9.4.5はこのHLSが使えないのです。一方、9.4.8は使えます。

そこで、GHC9.4.5で使えるHLSのバージョン2.2.0.0をインストールしてみましょう。一覧に表示されていないので「a」キーでみつけて「i」キーでインストールです。インストールが完了したらHLS2.2.0.0を「s」キーで選択し、GHCの項目を見に行きます。


HSL2.2.0.0が対応するGHC

今度は、GHC9.4.8に対応していません。

両方インストールしているので、HLSを必要に応じて切り替えるのもそれほど苦労はしません(ghcupを使えば一瞬で切り替えれる)が、GHC9.4.5でも最新のHLSを使いたいならば、自分でコンパイルすることもできます。

このコンパイルもTUI上から簡単にできます。

まず、TUI上のカーソルをHLS 2.9.0.1の上に持って行き、「リターン」キーを押すと、InstallCompileを選択するメニューが出るので、カーソルキーでCompileを選択して、「リターン」キーを押します。

Compileのメニューに target GHC(s) という行があるので、カーソルキーでそれを選択して、「リターン」キーを押します。そうすると、インストールされているGHCのバージョンが表示されるので、使えるようにしたいバージョンをカーソルキーで選択し、「リターン」キーでチェックを入れます。この時、使えるようにしたい複数のバージョンを全て選択します。


コンパイル設定画面

選択出来たら、「q」キーでメニューを戻っていきます。その他の設定は触る必要はありません。

メニューを戻ってComplieの部分がカーソルで選択された状態になったら、そこで、「リターン」キーを押すことで、コンパイルを開始することができます。

但し、このHLSのコンパイルには結構な時間がかかります!

特に、複数のバージョン選択をした場合には、マシンスペックにもよりますが、すごーく時間がかかります。なので、時間に余裕がある時に、またまたコーヒーでも飲みながら(今度はお湯を沸かして、ゆっくりドリップする時間も有るかもしれません)、のんびりとやりましょう。

GHCupのアンインストール

GHCupが必要なくなったら、以下のコマンドでアンインストールできます。

prompt
ghcup nuke

10秒間だけ、思いとどまる猶予をもらえます。

VSCodeでHaskellを書くための設定

VScodeはWindws上にインストールされているVSCodeを使います。もし、まだ持っていない場合は、MicroSoftのサイト等からWindwosにVSCodeをインストールしておいてください。

以下のエクステンションを VSCode にインストールします。

  • WSL
  • Haskell
  • Code Runner

エクステンションの管理は、VSCodeの左側にあるエクステンションアイコンから管理画面を開いてアクセスするのが簡単です。エクステンション管理画面の一番上にある検索窓を使って、インストールしたいエクステンションを探しましょう。


エクステンションの探し方

VSCodeでWSLの領域にアクセスする

まずは、WSLのエクステンションをインストールします。

VSCodeはWindowsアプリなので、通常、Windows上のディスク(ローカル)のファイルのみにアクセスします。しかし、WSLのエクステンションをインストールすることによって、ローカル以外である、WSL上のUbuntuにあるファイルにもアクセスすることが出来るようになります。

インストール後の使い方は次の通り。

  1. 左下のリモートへのアクセスアイコンをクリック
  2. メニューから「Connect to WSL」を選択


WSLへの接続の仕方

同様の方法でメニューから「Close Remote Connection」を選ぶことで、ローカルに戻ることが出来ます。

HaskellエクステンションでHLSを使う

次は、Haskellエクステンションをインストールします。WSLの時と同様にエクステンションアイコンからエクステンション画面を開いて検索窓からHaskellを検索してインストールします。

インストールが出来たら、Haskellエクステンションの設定画面から、HLSに関する設定を行います。

Haskellのエクステンションページを開いて、歯車マークでメニューを開きSettingsを選択します。


Haskellエクステンションの設定

設定ページが開いたら、Manage HLSの項目を探して、メニューからGHCupを選択します。

これで、VSCodeでHaskellのコードを書く時にエディタ上でHLSの支援を受けれるようになります。

Code Runnerエクステンションでコードを簡単実行

最後は、Code Runnerエクステンションをインストールします。

このエクステンションを入れると、エディタで開いているコードをCtl + Alt + nキーを押すだけで実行できるようになります。

Code Runnerはデフォルトでは、単にコードを実行してくれるだけです。しかしこの設定では、AtCoderやアルゴ式の問題で必要となる標準入力からのデータ取り込みを行う事が出来ません

そこで、Code Runnerでも、VSCodeのターミナルでコードを実行して、標準入力を受け取れる設定にします。

Code Runnerエクステンションの設定画面から、歯車マークをクリックすると出てくるメニューからSettingsを選びます。

設定画面の中にCode-Runner: Run In Terminalという項目を見つけて、チェックボックスにチェックを入れます。

この設定で、コードがターミナルで実行されるようになります。

NeovimでHaskellコードを書くための設定

Neovimの設定は多種多様なので、ネットで見かけるmason.nvim、mason-lspconfig.nvim、nvim-lspconfigの組み合わせにhaskell設定を行う場合の注意点を簡単に紹介します。

今回のLSPに関する大まかな設定の流れは以下のものが前提になります。

  • プラグイン管理はlazy.nvim
  • LSPの設定は、mason.nvim、mason-lspconfig.nvim、nvim-lspconfigの組み合わせ
  • 補完は、nvim-cmp

LSP関連の定番3種プラグインの連携概略

簡単に、3つのプラグインの関連性をおさらいすると次の通りです。

nvim-lspconfig

まず、nvim-lspconfgは、各言語毎のlspの設定を簡単にしてくれます。但し、ランゲージサーバーが有るのが前提なので、自分でインストールしなければなりません。

mason.nvim

次に、このプラグインは、lspに関する設定は何もしませんmason.nvimは、各言語のランゲージサーバーをneovim用にダウンロードしてmasonの中で管理してくれるプラグインです。

mason-lspconfig.nvim

以上から、masonを使ってランゲージサーバーを管理し、nvim-lspconfigで設定を書くという風に2つを使えば、neovim内でランゲージサーバーの管理を完結させたうえで、簡単に各言語のランゲージサーバーによる言語支援が受けられるようになります。

この部分をluaのランゲージサーバーで例示すれば次のようになります。

-- mason.nvimの設定
{
    'williamboman/mason.nvim',
    opts = {}
},

-- nvim-lspconfigでのlspの設定
{
    'neovim/nvim-lspconfig',
    config = function()
        local lspconfig = require('lspconfig')
        lspconfig.lua_ls.setup {}
    end
}

但し、この設定を書いたとしても、luaのランゲージサーバーのインストールを自動で行なってくれるわけではないので、一度だけ、自分でMasonを起動して、luaのランゲージサーバーをneovim内にインストールしないといけません。

また、Masonで新たにランゲージサーバーclangdをインストールしたとしても、それに対するLSPの設定が自動で書かれるわけではないので、自分でlspconfig.clangd.setup {}等と設定を追加しないと、clangの支援は受けれません。

そこで、これらの不便さを解消してくれるのがmason-lspconfig.nvimなのです。

その中でも一番カンタンに使えるのが、mason.nvimがダウンロードして管理しているランゲージサーバーをみて、そこにある各ランゲージサーバーの設定を私たちの代わりに自動で設定してくれる機能です。

具体的にはsetup_handlersという関数を利用して、サーバー名を汎用にしたLSP定義を書くことが出来ます。

この関数を使って、具体的に先の設定を改善したものが以下の設定です。

-- mason.nvimの設定
{
    'williamboman/mason.nvim',
    opts = {}
},

-- mason-lspconfig.nvimの設定
{
    'williamboman/mason-lspconfig.nvim',
    opts = {}
},

-- nvim-lspconfigでのlspの設定
{
    'neovim/nvim-lspconfig',
    config = function()
        local lspconfig = require('lspconfig')
        local mason_lspconfig = require('mason-lspconfig')

        mason_lspconfig.setup_handlers {
            function(server_name)
                lspconfig[server_name].setup{}
            end,
        }
    end
}

この設定を書いておけば、Masonでインストールした各ランゲージサーバーについて、自動でlspconfigのsetupを実行してくれます。

上記3種のプラグイン連携は、この流れを基本に組み立てられ、これに追加する形で、Mason経由で自動でインストールするランゲージサーバーを指示したり、ランゲージサーバー毎の個別のlspconfigセットアップオプションを定義したりしているのが、巷で紹介されているLSP設定です。

GHCup産のHLSをどう扱うか

3種連携の設定では、ランゲージサーバーがMason経由でインストールされることが前提でした。もちろん、HLSもMason経由でインストールして管理することができますが、決まったバージョンのHLSしかダウンロードすることが出来ません。

一方、私達はGHCupを使っているので、Masonがなくても、HLSを独自に用意できます。

以上のことから、私達の環境では、Haskell以外の言語は前述の連携LPS設定を使いつつ、Haskellについては、Masonの流れとは別にGHCup産のHLSを使う設定を書くことで対応します。

この具体的な設定を示すと次のようになります。

-- mason.nvimの設定
{
    'williamboman/mason.nvim',
    opts = {}
},

-- mason-lspconfig.nvimの設定
{
    'williamboman/mason-lspconfig.nvim',
    opts = {}
},

-- nvim-lspconfigでのlspの設定
{
    'neovim/nvim-lspconfig',
    config = function()
        local lspconfig = require('lspconfig')
        local mason_lspconfig = require('mason-lspconfig')

        -- Mason経由のランゲージサーバーの処理
        mason_lspconfig.setup_handlers {
            function(server_name)
                lspconfig[server_name].setup{}
            end,
        }

        -- 自分で用意したランゲージサーバーの設定
        lspconfig.hls.setup {
            -- GHCupで準備したHLSの名前
            cmd = {"haskell-language-server-wrapper", "--lsp"}
        }

    end
}

nvim-lspconfigのセットアップの中で、mason-lspconfigsetup-handlersとは別に、lspconfigのhlsのセットアップを行います。この時、cmdキーに対して、上述のコマンド名とオプションを渡してあげればOKです。

また、注意としては、Masonでhlsのインストールを行わないこと、及び、行なっている場合は、Mason内でアンインストールすることが必要になります。(そうしないと、二重設定になりトラブルのもとになります。)

lspsagaを追加する

lspの機能をもう少し便利にしてくれるプラグイン

https://nvimdev.github.io/lspsaga/

以下に設定例

 {
    'nvimdev/lspsaga.nvim',
    dependencies = {
        'nvim-treesitter/nvim-treesitter',
        'nvim-tree/nvim-web-devicons'
        },
    config = function()
        require('lspsaga').setup({})

        vim.keymap.set('n', 'K', '<cmd>Lspsaga hover_doc<CR>')
        vim.keymap.set('n', 'gr', '<cmd>Lspsaga finder<CR>')
        vim.keymap.set('n', 'ga', '<cmd>Lspsaga code_action<CR>')
    end,
},

最後に

(付録(仮))シリーズ?は、Haskellに関する初心者向けの本の付録部分になる予定の記事です。他に、WSLのインストール記事も少し前に書きましたが、これでなんとか、Haskellを触ってみようと思った時に実際に触り始めるための準備の道しるべが出来たかなぁと思います。

しかし、実際のところ、NeovimのLSPやランゲージサーバーの活用の部分については、書いているうちに沼にのめり込んでしまい(気がついたらvim盆栽しはじめていた、、)、他とのバランスがおかしくなったので、記事のおおかたを削除しました。

でも、わかってくると一般的なLSPの設定の話よりも、nvim-lspconfigでの設定雛形以外の、lsp設定プラグインだったり、Haskellに特化したコード支援のプラグインだったりをもう少し知りたいなぁと感じたので、それはそれで別の記事でまとめてみたいなぁと思いました。(lspsagaは、とりあえず入れるだけでも見た目が変わるので、追加しておきました。)

Discussion